Skip to content

Instantly share code, notes, and snippets.

@jjorissen52
Last active July 19, 2024 17:19
Show Gist options
  • Save jjorissen52/bde86dfffc22c75a1a17c1eb056fbde7 to your computer and use it in GitHub Desktop.
Save jjorissen52/bde86dfffc22c75a1a17c1eb056fbde7 to your computer and use it in GitHub Desktop.

Revisions

  1. jjorissen52 revised this gist Apr 16, 2021. 1 changed file with 29 additions and 0 deletions.
    29 changes: 29 additions & 0 deletions DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -259,5 +259,34 @@ subsets:
    ```

    # Migrations
    A good way to handle migrations is to run a `Job`:

    ```yaml
    kind: Job
    apiVersion: batch/v1
    metadata:
    name: migrate
    spec:
    backoffLimit: 10
    template:
    spec:
    volumes:
    - name: service-config
    secret:
    secretName: django-app-env
    containers:
    - name: migrate-app-db
    image: django-app
    command: [python, manage.py, migrate, --database, default]
    env:
    - name: ENV_FILE_PATH
    value: "/etc/env/.env"
    volumeMounts:
    - name: service-config
    mountPath: "/etc/env"
    readOnly: true
    ```
    # Apply
    Build your containers and run `kubectl -f apply` on each of the kubernetes files you made.
  2. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -160,7 +160,7 @@ events {
    }

    http {
    upstream store {
    upstream django_app {
    server localhost:8000;
    }

  3. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -233,7 +233,7 @@ http {
    ```
    # Database
    You have the option to use an external database or a [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/), but here we will talk about using an external database. You will need to create a `Service` and `Endpoint` for your Django app to connect to your database. Let's say your database server is bound to `172.20.20.5`:
    You have the option to use an external database or a [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/), but here we just assume you are using an external database. You will need to create a `Service` and `Endpoint` for your Django app to connect to your database. Let's say your database server is bound to `172.20.20.5`:
    ```yaml
    kind: "Service"
  4. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -26,7 +26,7 @@ CMD exec gunicorn --bind :$PORT --workers 1 --threads 4 my_app.wsgi
    You will also want to configure your app to pull from the environment or a file. I like to use `python-dotenv` for the best of both worlds.

    # Config
    You will need to make a kubernetes `Secret`, (the `DATABASE_HOST` setting shown below is assumed in the on databases). This file will ultimately be mounted to `/etc/env/.env`, so in `settings.py` you will want to load the values from there.
    You will need to make a kubernetes `Secret`, (the `DATABASE_HOST` setting shown below is assumed in the section on databases). This file will ultimately be mounted to `/etc/env/.env`, so in `settings.py` you will want to load the values from there.

    ```yaml
    apiVersion: v1
  5. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -49,7 +49,7 @@ stringData:
    ```
    # Serving Static Files
    Gunicorn and other suitable WSGI servers for Django do not server static files, so we will want to setup a reverse proxy server such as NGINX to take care of static files. In `settings.py`:
    Gunicorn and other suitable WSGI servers for Django do not serve static files, so we will want to setup a reverse proxy server such as NGINX to take care of static files. In `settings.py`:

    ```python
    STATIC_ROOT = os.environ.get('STATIC_ROOT_PATH', BASE_DIR / 'static')
  6. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -231,7 +231,7 @@ http {
    }
    }
    ```
    #Database
    # Database
    You have the option to use an external database or a [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/), but here we will talk about using an external database. You will need to create a `Service` and `Endpoint` for your Django app to connect to your database. Let's say your database server is bound to `172.20.20.5`:
  7. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -26,7 +26,7 @@ CMD exec gunicorn --bind :$PORT --workers 1 --threads 4 my_app.wsgi
    You will also want to configure your app to pull from the environment or a file. I like to use `python-dotenv` for the best of both worlds.

    # Config
    You will need to make a kubernetes `Secret`, e.g. (the `DATABASE_HOST` setting is assumed in section on databases). This file will ultimately be mounted to `/etc/env/.env` so in `settings.py` you will want to load the values from there.
    You will need to make a kubernetes `Secret`, (the `DATABASE_HOST` setting shown below is assumed in the on databases). This file will ultimately be mounted to `/etc/env/.env`, so in `settings.py` you will want to load the values from there.

    ```yaml
    apiVersion: v1
  8. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    If you are hosting a Django app with Kubernetes, there are a few things that you need to take care of.

    # Containterizing
    # Containerizing
    Here is a sample Dockerfile for a containerized app:

    ```Dockerfile
  9. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -233,7 +233,7 @@ http {
    ```
    #Database
    You have the option to use an external database or a [[StatefulSet|https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/]], but here we will talk about using an external database. You will need to create a `Service` and `Endpoint` for your Django app to connect to your database. Let's say your database server is bound to `172.20.20.5`:
    You have the option to use an external database or a [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/), but here we will talk about using an external database. You will need to create a `Service` and `Endpoint` for your Django app to connect to your database. Let's say your database server is bound to `172.20.20.5`:
    ```yaml
    kind: "Service"
  10. jjorissen52 revised this gist Apr 14, 2021. 1 changed file with 5 additions and 5 deletions.
    10 changes: 5 additions & 5 deletions DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    If you are hosting a Django app with Kubernetes, there are a few things that you need to take care of.

    ! Containterizing
    # Containterizing
    Here is a sample Dockerfile for a containerized app:

    ```Dockerfile
    @@ -25,7 +25,7 @@ CMD exec gunicorn --bind :$PORT --workers 1 --threads 4 my_app.wsgi

    You will also want to configure your app to pull from the environment or a file. I like to use `python-dotenv` for the best of both worlds.

    ! Config
    # Config
    You will need to make a kubernetes `Secret`, e.g. (the `DATABASE_HOST` setting is assumed in section on databases). This file will ultimately be mounted to `/etc/env/.env` so in `settings.py` you will want to load the values from there.

    ```yaml
    @@ -48,7 +48,7 @@ stringData:
    API_VERSION=
    ```
    ! Serving Static Files
    # Serving Static Files
    Gunicorn and other suitable WSGI servers for Django do not server static files, so we will want to setup a reverse proxy server such as NGINX to take care of static files. In `settings.py`:

    ```python
    @@ -231,7 +231,7 @@ http {
    }
    }
    ```
    !Database
    #Database
    You have the option to use an external database or a [[StatefulSet|https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/]], but here we will talk about using an external database. You will need to create a `Service` and `Endpoint` for your Django app to connect to your database. Let's say your database server is bound to `172.20.20.5`:
    @@ -259,5 +259,5 @@ subsets:
    ```

    ! Apply
    # Apply
    Build your containers and run `kubectl -f apply` on each of the kubernetes files you made.
  11. jjorissen52 revised this gist Apr 14, 2021. 2 changed files with 263 additions and 215 deletions.
    215 changes: 0 additions & 215 deletions DjangoKube.html
    Original file line number Diff line number Diff line change
    @@ -1,215 +0,0 @@
    <div class="tc-tiddler-body tc-reveal"><p>If you are hosting a Django app with Kubernetes, there are a few things that you need to take care of.</p><h1 class="">Containterizing</h1><p>Here is a sample Dockerfile for a containerized app:</p><pre class="dockerfile hljs"><code><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-buster

    <span class="hljs-keyword">RUN</span><span class="bash"> apt-get update \
    --fix-missing \
    &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

    <span class="hljs-keyword">ENV</span> APP_HOME /app
    <span class="hljs-keyword">WORKDIR</span><span class="bash"> <span class="hljs-variable">$APP_HOME</span></span>

    <span class="hljs-keyword">ADD</span><span class="bash"> requirements.txt requirements.txt</span>
    <span class="hljs-keyword">RUN</span><span class="bash"> pip install -r requirements.txt --no-cache-dir \
    &amp;&amp; rm requirements.txt</span>

    <span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

    <span class="hljs-keyword">ENV</span> PORT <span class="hljs-number">8000</span>
    <span class="hljs-keyword">CMD</span><span class="bash"> <span class="hljs-built_in">exec</span> gunicorn --<span class="hljs-built_in">bind</span> :<span class="hljs-variable">$PORT</span> --workers 1 --threads 4 my_app.wsgi</span></code></pre><p>You will also want to configure your app to pull from the environment or a file. I like to use <code>python-dotenv</code> for the best of both worlds.</p><h1 class="">Config</h1><p>You will need to make a kubernetes <code>Secret</code>, e.g. (the <code>DATABASE_HOST</code> setting is assumed in section on databases). This file will ultimately be mounted to <code>/etc/env/.env</code> so in <code>settings.py</code> you will want to load the values from there.</p><pre class="yaml hljs"><code><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">Secret</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">django-app-env</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">Opaque</span>
    <span class="hljs-attr">stringData:</span>
    <span class="hljs-string">.env:</span> <span class="hljs-string">|
    DEBUG=True
    USE_SQLITE=0
    ALLOWED_HOSTS=*,
    INITIAL_ADMIN_EMAIL=
    INITIAL_ADMIN_PASSWORD=
    DATABASE_NAME=
    DATABASE_USER=
    DATABASE_PASSWORD=
    DATABASE_HOST=app-db #
    API_VERSION=</span></code></pre><h1 class="">Serving Static Files</h1><p>Gunicorn and other suitable WSGI servers for Django do not server static files, so we will want to setup a reverse proxy server such as NGINX to take care of static files. In <code>settings.py</code>:</p><pre class="python hljs"><code>STATIC_ROOT = os.environ.get(<span class="hljs-string">'STATIC_ROOT_PATH'</span>, BASE_DIR / <span class="hljs-string">'static'</span>)</code></pre><p>Then we make a <code>Deployment</code> and <code>Service</code> where a volume mount is shared between the Django app container and the NGINX container so that the app's staticfiles can be collected into a location that the NGINX container can read on initialization (<code>initContainers</code>) :</p><pre class="yaml hljs"><code><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8000</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>
    <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-meta">---</span>
    <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">replicas:</span> <span class="hljs-number">2</span>
    <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service-config</span>
    <span class="hljs-attr">secret:</span>
    <span class="hljs-attr">secretName:</span> <span class="hljs-string">django-app-env</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">static</span>
    <span class="hljs-attr">emptyDir:</span> <span class="hljs-string">{</span> <span class="hljs-string">}</span>
    <span class="hljs-comment"># on container startup, move the collected staticfiles</span>
    <span class="hljs-attr">initContainers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">move-static</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">STATIC_ROOT_PATH</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"/usr/share/nginx/static"</span>
    <span class="hljs-attr">volumeMounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">static</span>
    <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/usr/share/nginx/static</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">[</span> <span class="hljs-string">python,</span> <span class="hljs-string">manage.py,</span> <span class="hljs-string">collectstatic,</span> <span class="hljs-string">--noinput</span> <span class="hljs-string">]</span>
    <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">app-nginx</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">app-nginx</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
    <span class="hljs-attr">volumeMounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">static</span>
    <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/usr/share/nginx/static</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ENV_FILE_PATH</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"/etc/env/.env"</span>
    <span class="hljs-attr">volumeMounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service-config</span>
    <span class="hljs-attr">mountPath:</span> <span class="hljs-string">"/etc/env"</span>
    <span class="hljs-attr">readOnly:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">livenessProbe:</span>
    <span class="hljs-attr">httpGet:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">8000</span>
    <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">3</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8000</span>
    <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">requests:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1"</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">"1024Mi"</span>
    <span class="hljs-attr">limits:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">"2"</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">"2048Mi"</span>

    </code></pre><p>Here is a sample NGINX Dockerfile and conf:</p><pre class="dockerfile hljs"><code><span class="hljs-comment"># Dockerfile</span>
    <span class="hljs-keyword">FROM</span> nginx:<span class="hljs-number">1.19</span>
    <span class="hljs-keyword">COPY</span><span class="bash"> nginx.conf /etc/nginx/nginx.conf</span></code></pre><pre><code># nginx.conf
    user nobody nogroup;
    # 'user nobody nobody;' for systems with 'nobody' as a group instead
    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;

    events {
    worker_connections 1024; # increase if you have lots of clients
    accept_mutex off; # set to 'on' if nginx worker_processes &gt; 1
    # 'use epoll;' to enable for Linux 2.6+
    # 'use kqueue;' to enable for FreeBSD, OSX
    }

    http {
    upstream store {
    server localhost:8000;
    }

    server {
    listen 8080;

    root /usr/share/nginx;
    location / {
    # checks for static file, if not found proxy to app
    try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_pass http://django_app;
    }
    }

    types {
    text/html html htm shtml;
    text/css css;
    text/xml xml rss;
    image/gif gif;
    image/jpeg jpeg jpg;
    application/x-javascript js;
    text/plain txt;
    text/x-component htc;
    text/mathml mml;
    image/png png;
    image/x-icon ico;
    image/x-jng jng;
    image/vnd.wap.wbmp wbmp;
    application/java-archive jar war ear;
    application/mac-binhex40 hqx;
    application/pdf pdf;
    application/x-cocoa cco;
    application/x-java-archive-diff jardiff;
    application/x-java-jnlp-file jnlp;
    application/x-makeself run;
    application/x-perl pl pm;
    application/x-pilot prc pdb;
    application/x-rar-compressed rar;
    application/x-redhat-package-manager rpm;
    application/x-sea sea;
    application/x-shockwave-flash swf;
    application/x-stuffit sit;
    application/x-tcl tcl tk;
    application/x-x509-ca-cert der pem crt;
    application/x-xpinstall xpi;
    application/zip zip;
    application/octet-stream deb;
    application/octet-stream bin exe dll;
    application/octet-stream dmg;
    application/octet-stream eot;
    application/octet-stream iso img;
    application/octet-stream msi msp msm;
    audio/mpeg mp3;
    audio/x-realaudio ra;
    video/mpeg mpeg mpg;
    video/quicktime mov;
    video/x-flv flv;
    video/x-msvideo avi;
    video/x-ms-wmv wmv;
    video/x-ms-asf asx asf;
    video/x-mng mng;
    }
    }</code></pre><h1 class="">Database</h1><p>You have the option to use an external database or a <a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/" class="tc-tiddlylink-external" target="_blank" rel="noopener noreferrer">StatefulSet</a>, but here we will talk about using an external database. You will need to create a <code>Service</code> and <code>Endpoint</code> for your Django app to connect to your database. Let's say your database server is bound to <code>172.20.20.5</code>:</p><pre class="yaml hljs"><code><span class="hljs-attr">kind:</span> <span class="hljs-string">"Service"</span>
    <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">"v1"</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">app-db</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">clusterIP:</span> <span class="hljs-string">None</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
    <span class="hljs-meta">---</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">"Endpoints"</span>
    <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">"v1"</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">app-db</span>
    <span class="hljs-attr">subsets:</span>
    <span class="hljs-bullet">-</span>
    <span class="hljs-attr">addresses:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">ip:</span> <span class="hljs-string">"172.20.20.5"</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">app-db</span>
    </code></pre><h1 class="">Apply</h1><p>Build your containers and run <code>kubectl -f apply</code> on each of the kubernetes files you made.</p></div>
    263 changes: 263 additions & 0 deletions DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,263 @@
    If you are hosting a Django app with Kubernetes, there are a few things that you need to take care of.

    ! Containterizing
    Here is a sample Dockerfile for a containerized app:

    ```Dockerfile
    FROM python:3.9-buster

    RUN apt-get update \
    --fix-missing \
    && rm -rf /var/lib/apt/lists/*

    ENV APP_HOME /app
    WORKDIR $APP_HOME

    ADD requirements.txt requirements.txt
    RUN pip install -r requirements.txt --no-cache-dir \
    && rm requirements.txt

    COPY . .

    ENV PORT 8000
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 4 my_app.wsgi
    ```

    You will also want to configure your app to pull from the environment or a file. I like to use `python-dotenv` for the best of both worlds.

    ! Config
    You will need to make a kubernetes `Secret`, e.g. (the `DATABASE_HOST` setting is assumed in section on databases). This file will ultimately be mounted to `/etc/env/.env` so in `settings.py` you will want to load the values from there.

    ```yaml
    apiVersion: v1
    kind: Secret
    metadata:
    name: django-app-env
    type: Opaque
    stringData:
    .env: |
    DEBUG=True
    USE_SQLITE=0
    ALLOWED_HOSTS=*,
    INITIAL_ADMIN_EMAIL=
    INITIAL_ADMIN_PASSWORD=
    DATABASE_NAME=
    DATABASE_USER=
    DATABASE_PASSWORD=
    DATABASE_HOST=app-db #
    API_VERSION=
    ```
    ! Serving Static Files
    Gunicorn and other suitable WSGI servers for Django do not server static files, so we will want to setup a reverse proxy server such as NGINX to take care of static files. In `settings.py`:

    ```python
    STATIC_ROOT = os.environ.get('STATIC_ROOT_PATH', BASE_DIR / 'static')
    ```

    Then we make a `Deployment` and `Service` where a volume mount is shared between the Django app container and the NGINX container so that the app's staticfiles can be collected into a location that the NGINX container can read on initialization (`initContainers`) :

    ```yaml
    apiVersion: v1
    kind: Service
    metadata:
    name: django-app
    spec:
    ports:
    - port: 8000
    targetPort: 8080
    type: LoadBalancer
    selector:
    app: django-app
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: django-app
    labels:
    app: django-app
    spec:
    replicas: 2
    selector:
    matchLabels:
    app: django-app
    template:
    metadata:
    labels:
    app: django-app
    spec:
    volumes:
    - name: service-config
    secret:
    secretName: django-app-env
    - name: static
    emptyDir: { }
    # on container startup, move the collected staticfiles
    initContainers:
    - name: move-static
    image: django-app
    env:
    - name: STATIC_ROOT_PATH
    value: "/usr/share/nginx/static"
    volumeMounts:
    - name: static
    mountPath: /usr/share/nginx/static
    command: [ python, manage.py, collectstatic, --noinput ]
    containers:
    - name: app-nginx
    image: app-nginx
    ports:
    - containerPort: 8080
    volumeMounts:
    - name: static
    mountPath: /usr/share/nginx/static
    - name: django-app
    image: django-app
    env:
    - name: ENV_FILE_PATH
    value: "/etc/env/.env"
    volumeMounts:
    - name: service-config
    mountPath: "/etc/env"
    readOnly: true
    livenessProbe:
    httpGet:
    path: /
    port: 8000
    initialDelaySeconds: 3
    ports:
    - containerPort: 8000
    resources:
    requests:
    cpu: "1"
    memory: "1024Mi"
    limits:
    cpu: "2"
    memory: "2048Mi"
    ```
    Here is a sample NGINX Dockerfile and conf:

    ```Dockerfile
    # Dockerfile
    FROM nginx:1.19
    COPY nginx.conf /etc/nginx/nginx.conf
    ```

    ```
    # nginx.conf
    user nobody nogroup;
    # 'user nobody nobody;' for systems with 'nobody' as a group instead
    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;

    events {
    worker_connections 1024; # increase if you have lots of clients
    accept_mutex off; # set to 'on' if nginx worker_processes > 1
    # 'use epoll;' to enable for Linux 2.6+
    # 'use kqueue;' to enable for FreeBSD, OSX
    }

    http {
    upstream store {
    server localhost:8000;
    }

    server {
    listen 8080;

    root /usr/share/nginx;
    location / {
    # checks for static file, if not found proxy to app
    try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_pass http://django_app;
    }
    }

    types {
    text/html html htm shtml;
    text/css css;
    text/xml xml rss;
    image/gif gif;
    image/jpeg jpeg jpg;
    application/x-javascript js;
    text/plain txt;
    text/x-component htc;
    text/mathml mml;
    image/png png;
    image/x-icon ico;
    image/x-jng jng;
    image/vnd.wap.wbmp wbmp;
    application/java-archive jar war ear;
    application/mac-binhex40 hqx;
    application/pdf pdf;
    application/x-cocoa cco;
    application/x-java-archive-diff jardiff;
    application/x-java-jnlp-file jnlp;
    application/x-makeself run;
    application/x-perl pl pm;
    application/x-pilot prc pdb;
    application/x-rar-compressed rar;
    application/x-redhat-package-manager rpm;
    application/x-sea sea;
    application/x-shockwave-flash swf;
    application/x-stuffit sit;
    application/x-tcl tcl tk;
    application/x-x509-ca-cert der pem crt;
    application/x-xpinstall xpi;
    application/zip zip;
    application/octet-stream deb;
    application/octet-stream bin exe dll;
    application/octet-stream dmg;
    application/octet-stream eot;
    application/octet-stream iso img;
    application/octet-stream msi msp msm;
    audio/mpeg mp3;
    audio/x-realaudio ra;
    video/mpeg mpeg mpg;
    video/quicktime mov;
    video/x-flv flv;
    video/x-msvideo avi;
    video/x-ms-wmv wmv;
    video/x-ms-asf asx asf;
    video/x-mng mng;
    }
    }
    ```
    !Database
    You have the option to use an external database or a [[StatefulSet|https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/]], but here we will talk about using an external database. You will need to create a `Service` and `Endpoint` for your Django app to connect to your database. Let's say your database server is bound to `172.20.20.5`:
    ```yaml
    kind: "Service"
    apiVersion: "v1"
    metadata:
    name: app-db
    spec:
    clusterIP: None
    ports:
    - port: 5432
    ---
    kind: "Endpoints"
    apiVersion: "v1"
    metadata:
    name: app-db
    subsets:
    -
    addresses:
    - ip: "172.20.20.5"
    ports:
    - port: 5432
    name: app-db
    ```

    ! Apply
    Build your containers and run `kubectl -f apply` on each of the kubernetes files you made.
  12. jjorissen52 renamed this gist Apr 14, 2021. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  13. jjorissen52 created this gist Apr 14, 2021.
    215 changes: 215 additions & 0 deletions DjangoKube.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,215 @@
    <div class="tc-tiddler-body tc-reveal"><p>If you are hosting a Django app with Kubernetes, there are a few things that you need to take care of.</p><h1 class="">Containterizing</h1><p>Here is a sample Dockerfile for a containerized app:</p><pre class="dockerfile hljs"><code><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-buster

    <span class="hljs-keyword">RUN</span><span class="bash"> apt-get update \
    --fix-missing \
    &amp;&amp; rm -rf /var/lib/apt/lists/*</span>

    <span class="hljs-keyword">ENV</span> APP_HOME /app
    <span class="hljs-keyword">WORKDIR</span><span class="bash"> <span class="hljs-variable">$APP_HOME</span></span>

    <span class="hljs-keyword">ADD</span><span class="bash"> requirements.txt requirements.txt</span>
    <span class="hljs-keyword">RUN</span><span class="bash"> pip install -r requirements.txt --no-cache-dir \
    &amp;&amp; rm requirements.txt</span>

    <span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

    <span class="hljs-keyword">ENV</span> PORT <span class="hljs-number">8000</span>
    <span class="hljs-keyword">CMD</span><span class="bash"> <span class="hljs-built_in">exec</span> gunicorn --<span class="hljs-built_in">bind</span> :<span class="hljs-variable">$PORT</span> --workers 1 --threads 4 my_app.wsgi</span></code></pre><p>You will also want to configure your app to pull from the environment or a file. I like to use <code>python-dotenv</code> for the best of both worlds.</p><h1 class="">Config</h1><p>You will need to make a kubernetes <code>Secret</code>, e.g. (the <code>DATABASE_HOST</code> setting is assumed in section on databases). This file will ultimately be mounted to <code>/etc/env/.env</code> so in <code>settings.py</code> you will want to load the values from there.</p><pre class="yaml hljs"><code><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">Secret</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">django-app-env</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">Opaque</span>
    <span class="hljs-attr">stringData:</span>
    <span class="hljs-string">.env:</span> <span class="hljs-string">|
    DEBUG=True
    USE_SQLITE=0
    ALLOWED_HOSTS=*,
    INITIAL_ADMIN_EMAIL=
    INITIAL_ADMIN_PASSWORD=
    DATABASE_NAME=
    DATABASE_USER=
    DATABASE_PASSWORD=
    DATABASE_HOST=app-db #
    API_VERSION=</span></code></pre><h1 class="">Serving Static Files</h1><p>Gunicorn and other suitable WSGI servers for Django do not server static files, so we will want to setup a reverse proxy server such as NGINX to take care of static files. In <code>settings.py</code>:</p><pre class="python hljs"><code>STATIC_ROOT = os.environ.get(<span class="hljs-string">'STATIC_ROOT_PATH'</span>, BASE_DIR / <span class="hljs-string">'static'</span>)</code></pre><p>Then we make a <code>Deployment</code> and <code>Service</code> where a volume mount is shared between the Django app container and the NGINX container so that the app's staticfiles can be collected into a location that the NGINX container can read on initialization (<code>initContainers</code>) :</p><pre class="yaml hljs"><code><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8000</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>
    <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-meta">---</span>
    <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">replicas:</span> <span class="hljs-number">2</span>
    <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service-config</span>
    <span class="hljs-attr">secret:</span>
    <span class="hljs-attr">secretName:</span> <span class="hljs-string">django-app-env</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">static</span>
    <span class="hljs-attr">emptyDir:</span> <span class="hljs-string">{</span> <span class="hljs-string">}</span>
    <span class="hljs-comment"># on container startup, move the collected staticfiles</span>
    <span class="hljs-attr">initContainers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">move-static</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">STATIC_ROOT_PATH</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"/usr/share/nginx/static"</span>
    <span class="hljs-attr">volumeMounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">static</span>
    <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/usr/share/nginx/static</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">[</span> <span class="hljs-string">python,</span> <span class="hljs-string">manage.py,</span> <span class="hljs-string">collectstatic,</span> <span class="hljs-string">--noinput</span> <span class="hljs-string">]</span>
    <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">app-nginx</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">app-nginx</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
    <span class="hljs-attr">volumeMounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">static</span>
    <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/usr/share/nginx/static</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">django-app</span>
    <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ENV_FILE_PATH</span>
    <span class="hljs-attr">value:</span> <span class="hljs-string">"/etc/env/.env"</span>
    <span class="hljs-attr">volumeMounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">service-config</span>
    <span class="hljs-attr">mountPath:</span> <span class="hljs-string">"/etc/env"</span>
    <span class="hljs-attr">readOnly:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">livenessProbe:</span>
    <span class="hljs-attr">httpGet:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">8000</span>
    <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">3</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8000</span>
    <span class="hljs-attr">resources:</span>
    <span class="hljs-attr">requests:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1"</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">"1024Mi"</span>
    <span class="hljs-attr">limits:</span>
    <span class="hljs-attr">cpu:</span> <span class="hljs-string">"2"</span>
    <span class="hljs-attr">memory:</span> <span class="hljs-string">"2048Mi"</span>

    </code></pre><p>Here is a sample NGINX Dockerfile and conf:</p><pre class="dockerfile hljs"><code><span class="hljs-comment"># Dockerfile</span>
    <span class="hljs-keyword">FROM</span> nginx:<span class="hljs-number">1.19</span>
    <span class="hljs-keyword">COPY</span><span class="bash"> nginx.conf /etc/nginx/nginx.conf</span></code></pre><pre><code># nginx.conf
    user nobody nogroup;
    # 'user nobody nobody;' for systems with 'nobody' as a group instead
    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;

    events {
    worker_connections 1024; # increase if you have lots of clients
    accept_mutex off; # set to 'on' if nginx worker_processes &gt; 1
    # 'use epoll;' to enable for Linux 2.6+
    # 'use kqueue;' to enable for FreeBSD, OSX
    }

    http {
    upstream store {
    server localhost:8000;
    }

    server {
    listen 8080;

    root /usr/share/nginx;
    location / {
    # checks for static file, if not found proxy to app
    try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_pass http://django_app;
    }
    }

    types {
    text/html html htm shtml;
    text/css css;
    text/xml xml rss;
    image/gif gif;
    image/jpeg jpeg jpg;
    application/x-javascript js;
    text/plain txt;
    text/x-component htc;
    text/mathml mml;
    image/png png;
    image/x-icon ico;
    image/x-jng jng;
    image/vnd.wap.wbmp wbmp;
    application/java-archive jar war ear;
    application/mac-binhex40 hqx;
    application/pdf pdf;
    application/x-cocoa cco;
    application/x-java-archive-diff jardiff;
    application/x-java-jnlp-file jnlp;
    application/x-makeself run;
    application/x-perl pl pm;
    application/x-pilot prc pdb;
    application/x-rar-compressed rar;
    application/x-redhat-package-manager rpm;
    application/x-sea sea;
    application/x-shockwave-flash swf;
    application/x-stuffit sit;
    application/x-tcl tcl tk;
    application/x-x509-ca-cert der pem crt;
    application/x-xpinstall xpi;
    application/zip zip;
    application/octet-stream deb;
    application/octet-stream bin exe dll;
    application/octet-stream dmg;
    application/octet-stream eot;
    application/octet-stream iso img;
    application/octet-stream msi msp msm;
    audio/mpeg mp3;
    audio/x-realaudio ra;
    video/mpeg mpeg mpg;
    video/quicktime mov;
    video/x-flv flv;
    video/x-msvideo avi;
    video/x-ms-wmv wmv;
    video/x-ms-asf asx asf;
    video/x-mng mng;
    }
    }</code></pre><h1 class="">Database</h1><p>You have the option to use an external database or a <a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/" class="tc-tiddlylink-external" target="_blank" rel="noopener noreferrer">StatefulSet</a>, but here we will talk about using an external database. You will need to create a <code>Service</code> and <code>Endpoint</code> for your Django app to connect to your database. Let's say your database server is bound to <code>172.20.20.5</code>:</p><pre class="yaml hljs"><code><span class="hljs-attr">kind:</span> <span class="hljs-string">"Service"</span>
    <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">"v1"</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">app-db</span>
    <span class="hljs-attr">spec:</span>
    <span class="hljs-attr">clusterIP:</span> <span class="hljs-string">None</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
    <span class="hljs-meta">---</span>
    <span class="hljs-attr">kind:</span> <span class="hljs-string">"Endpoints"</span>
    <span class="hljs-attr">apiVersion:</span> <span class="hljs-string">"v1"</span>
    <span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">app-db</span>
    <span class="hljs-attr">subsets:</span>
    <span class="hljs-bullet">-</span>
    <span class="hljs-attr">addresses:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">ip:</span> <span class="hljs-string">"172.20.20.5"</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">5432</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">app-db</span>
    </code></pre><h1 class="">Apply</h1><p>Build your containers and run <code>kubectl -f apply</code> on each of the kubernetes files you made.</p></div>