{"id":20285413,"url":"https://github.com/mramshaw/cloud_django","last_synced_at":"2026-02-16T07:32:59.825Z","repository":{"id":29358961,"uuid":"121455671","full_name":"mramshaw/Cloud_Django","owner":"mramshaw","description":"Django cloudified","archived":false,"fork":false,"pushed_at":"2024-09-04T20:34:26.000Z","size":323,"stargazers_count":1,"open_issues_count":25,"forks_count":1,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-18T18:46:10.039Z","etag":null,"topics":["django","docker","gunicorn","kubernetes","minikube","postgres","postgresql","psycopg2","python"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mramshaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-14T01:00:27.000Z","updated_at":"2019-01-10T21:54:11.000Z","dependencies_parsed_at":"2023-01-14T14:46:13.420Z","dependency_job_id":"8c92c6bf-4c7f-4c61-93ec-86d2bfa7ca3f","html_url":"https://github.com/mramshaw/Cloud_Django","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mramshaw/Cloud_Django","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mramshaw","download_url":"https://codeload.github.com/mramshaw/Cloud_Django/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mramshaw%2FCloud_Django/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29502934,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-16T05:57:17.024Z","status":"ssl_error","status_checked_at":"2026-02-16T05:56:49.929Z","response_time":115,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","docker","gunicorn","kubernetes","minikube","postgres","postgresql","psycopg2","python"],"created_at":"2024-11-14T14:26:30.988Z","updated_at":"2026-02-16T07:32:59.801Z","avatar_url":"https://github.com/mramshaw.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cloud Django\n\n[![Known Vulnerabilities](http://snyk.io/test/github/mramshaw/Cloud_Django/badge.svg?style=plastic\u0026targetFile=requirements.txt)](http://snyk.io/test/github/mramshaw/Cloud_Django?style=plastic\u0026targetFile=requirements.txt)\n\nThis project follows on from my [Writing_Django](https://github.com/mramshaw/Writing_Django) project, which is a simple Hello World in Django.\n\nIt will use [gunicorn](http://gunicorn.org/) which is a web server for [Django](https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/gunicorn/).\nSpecifically, it is a [WSGI](https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface) server.\n\nIt will also use [Docker](https://github.com/mramshaw/Docker) and [Kubernetes](https://github.com/mramshaw/Kubernetes)\n (initially via [minikube](https://github.com/kubernetes/minikube)).\n\nIt will be migrated to [PostgreSQL](https://www.postgresql.org/) for persistence via the Python Postgres module `psycopg2`.\n\nThe plan of attack is as follows:\n\n* [Install and test 'gunicorn'](https://github.com/mramshaw/Cloud_Django#gunicorn)\n* [Dockerize our app](https://github.com/mramshaw/Cloud_Django#docker)\n    * [Django Configuration (minikube)](https://github.com/mramshaw/Cloud_Django#django-configuration-minikube)\n    * [Docker build (1.0)](https://github.com/mramshaw/Cloud_Django#docker-build-10)\n    * [Docker versions](https://github.com/mramshaw/Cloud_Django#docker-versions)\n* [Run our Dockerized app (1.0)](https://github.com/mramshaw/Cloud_Django#run-our-dockerized-app-10)\n    * [minikube (local Kubernetes)](https://github.com/mramshaw/Cloud_Django#minikube-local-kubernetes)\n    * [Run Dockerized app](https://github.com/mramshaw/Cloud_Django#run-dockerized-app)\n        * [Check polls deployment](https://github.com/mramshaw/Cloud_Django#check-polls-deployment)\n        * [Scale up](https://github.com/mramshaw/Cloud_Django#scale-up)\n        * [Check polls service](https://github.com/mramshaw/Cloud_Django#check-polls-service)\n        * [Sticky sessions](https://github.com/mramshaw/Cloud_Django#sticky-sessions)\n    * [Teardown](https://github.com/mramshaw/Cloud_Django#teardown)\n* [Migrate our app to PostgreSQL](https://github.com/mramshaw/Cloud_Django#migration-to-postgres)\n    * [psycopg2](https://github.com/mramshaw/Cloud_Django#psycopg2)\n    * [Docker (postgres)](https://github.com/mramshaw/Cloud_Django#docker-postgres)\n    * [Django Configuration (local postgres)](https://github.com/mramshaw/Cloud_Django#django-configuration-local-postgres)\n    * [Create postgres backend](https://github.com/mramshaw/Cloud_Django#create-postgres-backend)\n        * [minikube](https://github.com/mramshaw/Cloud_Django#minikube)\n        * [Create test database](https://github.com/mramshaw/Cloud_Django#create-test-database)\n        * [Run Django migrations](https://github.com/mramshaw/Cloud_Django#run-django-migrations)\n        * [Create test data](https://github.com/mramshaw/Cloud_Django#create-test-data)\n    * [Create Admin user](https://github.com/mramshaw/Cloud_Django#create-admin-user)\n    * [Test local Django](https://github.com/mramshaw/Cloud_Django#test-local-django)\n    * [Docker build (2.0)](https://github.com/mramshaw/Cloud_Django#docker-build-postgres-20-build-includes-psycopg2)\n    * [Replicated Django](https://github.com/mramshaw/Cloud_Django#replicated-django)\n* [Run our Dockerized app (2.0)](https://github.com/mramshaw/Cloud_Django#run-our-dockerized-app-20)\n* [Final teardown](https://github.com/mramshaw/Cloud_Django#final-teardown)\n* [Software Versions](https://github.com/mramshaw/Cloud_Django#versions)\n* [Still To Do](https://github.com/mramshaw/Cloud_Django#to-do)\n\nOnce all of this has been carried out locally, our app will be ready to be deployed in the cloud (AWS, Azure, GCP, etc.).\n\nOne of the convenient things about __minikube__ is that it is local; this saves a lot of upload/download time. It's ___fast___.\n\n# gunicorn\n\nTo install locally:\n\n    $ pip install --user gunicorn\n\nAs usual, replace `pip` with `pip3` for Python3.\n\nTo verify the version:\n\n    $ pip list --format=freeze | grep gunicorn\n\nOr simply use the `requirements.txt` file:\n\n    $ pip install --user -r requirements.txt\n\nLets see if it runs (this needs to be in the same folder as `manage.py`):\n\n    $ gunicorn polls.wsgi\n    [2018-02-13 17:30:31 +0000] [6232] [INFO] Starting gunicorn 19.7.1\n    [2018-02-13 17:30:31 +0000] [6232] [INFO] Listening at: http://127.0.0.1:8000 (6232)\n    [2018-02-13 17:30:31 +0000] [6232] [INFO] Using worker: sync\n    [2018-02-13 17:30:31 +0000] [6236] [INFO] Booting worker with pid: 6236\n    Not Found: /\n    Not Found: /favicon.ico\n    Not Found: /favicon.ico\n    ^C[2018-02-13 17:31:06 +0000] [6232] [INFO] Handling signal: int\n    [2018-02-14 01:31:06 +0000] [6236] [INFO] Worker exiting (pid: 6236)\n    [2018-02-13 17:31:06 +0000] [6232] [INFO] Shutting down: Master\n    $\n\nNow we need to open up `gunicorn` with a `gunicorn.conf` file. By default, `gunicorn`\nruns locally and will only accept local connections. We will configure it to run in\n__promiscuous mode__ (which is a terrible practice, we should really run it behind a\nfront-end, but we can fix this later).\n\nOnce more, with a config file:\n\n    $ gunicorn -c ../gunicorn.conf polls.wsgi\n    [2018-02-18 15:24:35 +0000] [16256] [INFO] Starting gunicorn 19.7.1\n    [2018-02-18 15:24:35 +0000] [16256] [INFO] Listening at: http://0.0.0.0:8000 (16256)\n    [2018-02-18 15:24:35 +0000] [16256] [INFO] Using worker: sync\n    [2018-02-18 15:24:35 +0000] [16260] [INFO] Booting worker with pid: 16260\n    ...\n    ^C[2018-02-18 15:24:37 +0000] [16256] [INFO] Handling signal: int\n    [2018-02-18 23:24:37 +0000] [16299] [INFO] Worker exiting (pid: 16299)\n    ...\n    [2018-02-18 23:24:37 +0000] [16285] [INFO] Worker exiting (pid: 16285)\n    [2018-02-18 15:24:37 +0000] [16256] [INFO] Shutting down: Master\n    $\n\nOkay, everything runs.\n\n# Docker\n\nLets see if we can save some Docker build time:\n\n    $ sudo docker search python\n    ....\n    $ sudo docker search django\n    ....\n    $ sudo docker search gunicorn\n    ....\n\nHmm, maybe it would be faster to do a search from:\n\n    https://hub.docker.com/explore/\n\nOr maybe not, it looks like the official Django image is __deprecated__:\n\n    https://hub.docker.com/_/django/\n\n[This page has a link to the Docker Store version of Django: 404]\n\nOkay, the recommendation is to start with a Python image, lets do that.\n\nThe options are endless:\n\n    https://hub.docker.com/_/python/\n\n[Of course, it doesn't really make any sense to Dockerize an app with a bundled database - but we will address that later.]\n\n## Django Configuration (minikube)\n\nWe need to change our Django settings to allow '192.168.99.100' (minikube traffic)\nin addition to '127.0.0.1' (local traffic). Change the line `ALLOWED_HOSTS = []` in\n`polls/settings.py` as follows:\n\n    ALLOWED_HOSTS = ['127.0.0.1', '192.168.99.100']\n\n[We should revisit these settings once everything runs behind a front-end.]\n\n## Docker build (1.0)\n\nLets build our dockerized app:\n\n    $ sudo docker build -f Dockerfile_1.0 -t mramshaw4docs/python-django-gunicorn:1.0 .\n\nAvailable from DockerHub [here](https://hub.docker.com/r/mramshaw4docs/python-django-gunicorn/).\n\n## Docker versions\n\nOkay, lets check the software versions bundled in our Dockerized app:\n\n    $ docker run --rm -it mramshaw4docs/python-django-gunicorn /bin/sh\n    /usr/src/app # python --version\n    Python 3.6.4\n    /usr/src/app # python -m django --version\n    1.11.10\n    /usr/src/app # gunicorn --version\n    gunicorn (version 19.7.1)\n    /usr/src/app # exit\n    $\n\nApart from the Python version, the same software as in the original [Writing_Django](https://github.com/mramshaw/Writing_Django) project.\n\n# Run our Dockerized app (1.0)\n\nLets quickly run and - more importantly - ___test___ our Dockerized app.\n\n## minikube (local Kubernetes)\n\nStart minikube:\n\n    $ minikube start\n    Starting local Kubernetes v1.9.0 cluster...\n    Starting VM...\n    Getting VM IP address...\n    Moving files into cluster...\n    Setting up certs...\n    Connecting to cluster...\n    Setting up kubeconfig...\n    Starting cluster components...\n    Kubectl is now configured to use the cluster.\n    Loading cached images from config file.\n    $\n\n## Run Dockerized app\n\nRun our dockerized app:\n\n    $ kubectl create -f ./polls.yaml\n\n#### Check polls deployment\n\nLets check our deployment first. Port-forward our pod (as usual, Ctrl-C to terminate):\n\n    $ kubectl port-forward polls-7bd58769c7-r27rr 8000:8000\n    Forwarding from 127.0.0.1:8000 -\u003e 8000\n    Handling connection for 8000\n    ....\n\nThis will make our app available at `127.0.0.1:8000` where we will test it:\n\n![Minikubed_App](images/minikubed_app.png)\n\nOkay, so now we know the deployment portion of our `yaml` is good. We can kill our port-forwarding now (Ctrl-C to terminate):\n\n    Handling connection for 8000\n    ^C$\n\n#### Scale up\n\nLets increase the number of replicas to 2. Edit `polls.yaml`, save it, and apply it:\n\n    $ kubectl apply -f ./polls.yaml\n    Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply\n    deployment \"polls\" configured\n    Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply\n    service \"polls\" configured\n    $\n\nThe warnings are because these resources were created with `kubectl create`. If we change the replicas back to 1 and run `kubectl apply` again:\n\n    $ kubectl apply -f ./polls.yaml\n    deployment \"polls\" configured\n    service \"polls\" unchanged\n    $\n\nSo lets increase the number of replicas to 2 again:\n\n    $ kubectl apply -f ./polls.yaml\n    deployment \"polls\" configured\n    service \"polls\" unchanged\n    $\n\n[Note no warning this time; the last `kubectl` was an `apply`.]\n\nNow we will port-forward each of our Kubernetes pods and change each poll question so each pod has a different question:\n\n    $ kubectl port-forward polls-7cd964c89-4px25 8000:8000 \u0026\n    [1] 5899\n    $ Forwarding from 127.0.0.1:8000 -\u003e 8000\n    kubectl port-forward polls-7cd964c89-wqkxc 8001:8000 \u0026\n    [2] 5912\n    $\n\n__Pod 1__\n\n![Pod_1_Question_1](images/Pod_1_Question_1.png)\n\n__Pod 2__\n\n![Pod_2_Question_2](images/Pod_2_Question_2.png)\n\n#### Check polls service\n\nNow we can try to access our Kubernetes pods through a Kubernetes service\n(this will pop open a browser):\n\n    $ minikube service polls\n\nWe can access our service now (note that this is a\n[NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport)\nservice), but things are pretty hit-and-miss. Most of the time we cannot even\nlog in, and when we can which pod we get is kind of a crap-shoot (that's why\nwe made sure each pod had different questions, so we could see which one we\nwere accessing).\n\n#### Sticky sessions\n\nLets see if 'sticky sessions' help at all:\n\n    $ kubectl apply -f ./polls-svc-sticky.yaml\n    service \"polls\" configured\n    $\n\nYes, that definitely does seem to improve things - no more inconsistent\nbehaviour (eventually we will rely on our front-end for session management,\nthis was simply a little experiment). Now that we know that the service part\nof `polls.yaml` works, we simply need to make our app reference a non-bundled\nback-end.\n\n[Although it *works*, it looks pretty horrible as our `static` content is missing.\n We can fix that later, probably with a front-end or an `ingress` controller.]\n\n## Teardown\n\nLets teardown our local Kubernetes infrastructure:\n\n    $ kubectl delete svc/polls deploy/polls\n\nFinally:\n\n    $ minikube stop\n    Stopping local Kubernetes cluster...\n    Machine stopped.\n    $\n\n# Migration to postgres\n\nPostgres is a mature database that offers replication, which is important for scaling in the cloud.\nIt has had _streaming_ replication since PostgreSQL 9 and - with PostgreSQL 10 - offers _logical_\nreplication.\n\n[Streaming replication is _physical_ replication, where the replicas are binary copies. Replication\n is at the block level with changes of these blindly propagated to the replicas. With _logical_\n replication, record level changes are propagated. And these can be on a table by table, or even\n record by record, basis. Think _sharding_.]\n\nWe will repeat most of the steps listed here:\n\n    https://docs.djangoproject.com/en/1.11/intro/tutorial02/\n\nHowever, we will be using __postgres__ instead of __sqlite__.\n\n## psycopg2\n\nWe will need the Python Postgres module `psycopg2`:\n\n    $ pip install --user psycopg2\n\n[As usual, replace with `pip3` for Python3.]\n\nVerify the latest version:\n\n    $ pip list --format=freeze | grep psycopg2\n    psycopg2 (2.7.4)\n    $\n\n[2.7.4]\n\n## Docker (postgres)\n\nWe will use the official Docker image for `postgres`:\n\n    https://hub.docker.com/_/postgres/\n\nRun it:\n\n    $ docker run --rm -it --name polls-postgres -d postgres:10.2-alpine\n\nAnd a quick smoke test:\n\n    $ docker run -it --rm --link polls-postgres:postgres postgres:10.2-alpine psql -h postgres -U postgres\n    psql (10.2)\n    Type \"help\" for help.\n    \n    postgres=# \\conninfo\n    You are connected to database \"postgres\" as user \"postgres\" on host \"postgres\" at port \"5432\".\n    postgres=# SELECT 1;\n     ?column? \n    ----------\n            1\n    (1 row)\n    \n    postgres=#\n\nAnd now the __important part__ - creating our database:\n\n    postgres=# CREATE DATABASE polls;\n    CREATE DATABASE\n    postgres=# \\q\n    $\n\nOkay, we have a functional `postgres`. Lets tear it down:\n\n    $ docker kill polls-postgres\n\nWe will go through most of this again in [Create postgres backend](#create-postgres-backend) shortly.\n\n## Django Configuration (local postgres)\n\nChange `polls/settings.py` as follows. First insert:\n\n    DATABASES = {\n        'default': {\n            'ENGINE': 'django.db.backends.postgresql_psycopg2',\n            'NAME': 'polls',\n            'USER': 'postgres',\n            'PASSWORD': 'postgres',\n            'HOST': '127.0.0.1',\n            'PORT': 5432\n        }\n    }\n\nThen remove or comment:\n\n    DATABASES = {\n        'default': {\n            'ENGINE': 'django.db.backends.sqlite3',\n            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),\n        }\n    }\n\n[This uses __default passwords__ which is generally a very lazy and insecure practice.\nIt's actually even worse as we are using the __root__ postgres user and password.\nThe __default__ root user and password. We will need to harden all of this before\nwe get too much further, but especially before any move into production.]\n\n## Create postgres backend\n\n#### minikube\n\nStart minikube:\n\n    $ minikube start\n    ....\n\nStart `postgres`:\n\n    $ kubectl create -f ./postgres.yaml\n\n#### Create test database\n\nShell into our backend pod and create our `polls` database:\n\n    $ kubectl exec -it polls-backend-67c86654df-xll2q sh\n    / # psql -U postgres\n    psql (10.2)\n    Type \"help\" for help.\n    \n    postgres=# \\conninfo\n    You are connected to database \"postgres\" as user \"postgres\" via socket in \"/var/run/postgresql\" at port \"5432\".\n    postgres=#\n\n[This would be the point at which to ___harden___ everything with the `\\password postgres` command and so forth,\n but for testing purposes we will not do this.]\n\n    postgres=# CREATE DATABASE polls;\n    CREATE DATABASE\n    postgres=# \\q\n    / # exit\n    $\n\n#### Run Django migrations\n\nMuch like PHP's Laravel framework, Django will handle SQL generation and migration for any defined data structures.\n\nPort-forward our postgres pod (as usual, Ctrl-C to terminate):\n\n    $ kubectl port-forward polls-backend-67c86654df-xll2q 5432:5432\n\nIn a new window, run our Django server (needs to be in the folder where `manage.py` resides):\n\n    $ cd polls\n    $ ls -al\n    total 64\n    drwxrwxr-x 4 owner owner  4096 Feb 15 22:46 .\n    drwxrwxr-x 5 owner owner  4096 Feb 16 08:27 ..\n    -rw-r--r-- 1 owner owner 41984 Feb 12 10:24 db.sqlite3\n    -rwxrwxr-x 1 owner owner   803 Feb 11 13:40 manage.py\n    drwxrwxr-x 2 owner owner  4096 Feb 15 19:31 polls\n    drwxrwxr-x 3 owner owner  4096 Feb 15 11:58 polls_app\n    $ python manage.py runserver\n    Performing system checks...\n    \n    System check identified no issues (0 silenced).\n    \n    You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, polls_app, sessions.\n    Run 'python manage.py migrate' to apply them.\n    \n    February 15, 2018 - 22:09:42\n    Django version 1.11.10, using settings 'polls.settings'\n    Starting development server at http://127.0.0.1:8000/\n    Quit the server with CONTROL-C.\n    Not Found: /\n    [15/Feb/2018 22:12:35] \"GET / HTTP/1.1\" 404 2017\n    ^C$\n\nBefore killing it, check that it runs:\n\n![Local_App_Minikubed](images/Local_App_Minikubed.png)\n\nLets check our [database migrations](https://docs.djangoproject.com/en/1.11/topics/migrations/):\n\n    $ python manage.py showmigrations\n    admin\n     [ ] 0001_initial\n     [ ] 0002_logentry_remove_auto_add\n    auth\n     [ ] 0001_initial\n     [ ] 0002_alter_permission_name_max_length\n     [ ] 0003_alter_user_email_max_length\n     [ ] 0004_alter_user_username_opts\n     [ ] 0005_alter_user_last_login_null\n     [ ] 0006_require_contenttypes_0002\n     [ ] 0007_alter_validators_add_error_messages\n     [ ] 0008_alter_user_username_max_length\n    contenttypes\n     [ ] 0001_initial\n     [ ] 0002_remove_content_type_name\n    polls_app\n     [ ] 0001_initial\n    sessions\n     [ ] 0001_initial\n    $\n\nAnd run them:\n\n    $ python manage.py migrate\n    Operations to perform:\n      Apply all migrations: admin, auth, contenttypes, polls_app, sessions\n    Running migrations:\n      Applying contenttypes.0001_initial... OK\n      Applying auth.0001_initial... OK\n      Applying admin.0001_initial... OK\n      Applying admin.0002_logentry_remove_auto_add... OK\n      Applying contenttypes.0002_remove_content_type_name... OK\n      Applying auth.0002_alter_permission_name_max_length... OK\n      Applying auth.0003_alter_user_email_max_length... OK\n      Applying auth.0004_alter_user_username_opts... OK\n      Applying auth.0005_alter_user_last_login_null... OK\n      Applying auth.0006_require_contenttypes_0002... OK\n      Applying auth.0007_alter_validators_add_error_messages... OK\n      Applying auth.0008_alter_user_username_max_length... OK\n      Applying polls_app.0001_initial... OK\n      Applying sessions.0001_initial... OK\n    $\n\nSo now we can create our app migration(s):\n\n    $ python manage.py makemigrations polls_app\n    No changes detected in app 'polls_app'\n    $\n\nInterestingly, this is __not__ what I was expecting. Lets double-check:\n\n    $ mv polls_app/migrations/0001_initial.py polls_app/migrations/0001_initial.py.orig\n    $ python manage.py makemigrations polls_app\n    Migrations for 'polls_app':\n      polls_app/migrations/0001_initial.py\n        - Create model Choice\n        - Create model Question\n        - Add field question to choice\n    $ diff -uw polls_app/migrations/0001_initial.py.orig polls_app/migrations/0001_initial.py\n    --- polls_app/migrations/0001_initial.py.orig\t2018-02-11 15:32:37.401180000 -0800\n    +++ polls_app/migrations/0001_initial.py\t2018-02-15 14:28:24.501089257 -0800\n    @@ -1,5 +1,5 @@\n     # -*- coding: utf-8 -*-\n    -# Generated by Django 1.11.10 on 2018-02-11 23:32\n    +# Generated by Django 1.11.10 on 2018-02-15 22:28\n     from __future__ import unicode_literals\n     \n     from django.db import migrations, models\n    $\n\nOkay, looks like everything is copacetic:\n\n    $ rm polls_app/migrations/0001_initial.py.orig\n\nLets see if the generated SQL is any different from the __sqlite__ SQL:\n\n    $ python manage.py sqlmigrate polls_app 0001\n    BEGIN;\n    --\n    -- Create model Choice\n    --\n    CREATE TABLE \"polls_app_choice\" (\"id\" serial NOT NULL PRIMARY KEY, \"choice_text\" varchar(200) NOT NULL, \"votes\" integer NOT NULL);\n    --\n    -- Create model Question\n    --\n    CREATE TABLE \"polls_app_question\" (\"id\" serial NOT NULL PRIMARY KEY, \"question_text\" varchar(200) NOT NULL, \"pub_date\" timestamp with time zone NOT NULL);\n    --\n    -- Add field question to choice\n    --\n    ALTER TABLE \"polls_app_choice\" ADD COLUMN \"question_id\" integer NOT NULL;\n    CREATE INDEX \"polls_app_choice_question_id_38df74ee\" ON \"polls_app_choice\" (\"question_id\");\n    ALTER TABLE \"polls_app_choice\" ADD CONSTRAINT \"polls_app_choice_question_id_38df74ee_fk_polls_app_question_id\" FOREIGN KEY (\"question_id\") REFERENCES \"polls_app_question\" (\"id\") DEFERRABLE INITIALLY DEFERRED;\n    COMMIT;\n    $\n\n[Yep, some very slight differences, minor SQL dialect issues.]\n\nCheck for any issues:\n\n    $ python manage.py check\n    System check identified no issues (0 silenced).\n    $\n\nAnd __migrate__ again to apply our migrations:\n\n    $ python manage.py migrate\n    Operations to perform:\n      Apply all migrations: admin, auth, contenttypes, polls_app, sessions\n    Running migrations:\n      No migrations to apply.\n    $\n\n[Not quite what I expected, but makes sense as there haven't been any changes. If we check __showmigrations__ we will see everything is fine.]\n\n#### Create test data\n\nNow we will create some (different) poll questions:\n\n    $ python manage.py shell\n    Python 2.7.12 (default, Dec  4 2017, 14:50:18) \n    Type \"copyright\", \"credits\" or \"license\" for more information.\n    \n    IPython 2.4.1 -- An enhanced Interactive Python.\n    ?         -\u003e Introduction and overview of IPython's features.\n    %quickref -\u003e Quick reference.\n    help      -\u003e Python's own help system.\n    object?   -\u003e Details about 'object', use 'object??' for extra details.\n    \n    In [1]: from polls_app.models import Question, Choice\n    \n    In [2]: Question.objects.all()\n    Out[2]: \u003cQuerySet []\u003e\n    \n    In [3]: from django.utils import timezone\n    \n    In [4]: q = Question(question_text=\"Is Django better than Flask?\", pub_date=timezone.now())\n    \n    In [5]: q.save()\n    \n    In [6]: q = Question(question_text=\"Is Python3 better than Python2?\", pub_date=timezone.now())\n    \n    In [7]: q.save()\n    \n    In [8]: Question.objects.all()\n    Out[8]: \u003cQuerySet [\u003cQuestion: Is Django better than Flask?\u003e, \u003cQuestion: Is Python3 better than Python2?\u003e]\u003e\n    \n    In [9]: quit\n    $\n\n## Create Admin user\n\nCreate an Admin user ('admin' and '123abcde' again):\n\n    $ python manage.py createsuperuser\n    Username (leave blank to use 'owner'): admin\n    Email address: admin@example.com\n    Password: \n    Password (again): \n    Superuser created successfully.\n    $\n\n## Test local Django\n\nRun our (local) server:\n\n    $ python manage.py runserver\n    ....\n\nLog in:\n\n![Local_App_Cloudified_Backend](images/Local_App_Cloudified_Backend.png)\n\nOkay, we have a configured `postgres` backend. Now we can replicate our Django pods.\n\nWe can kill our local server, as well as our `postgres` port-forwarding (but __not__ our postgres pod!).\n\n## Docker build (postgres 2.0 build, includes 'psycopg2')\n\nLets build our dockerized app again - this will be the __2.0__ version with postgres support:\n\n    $ sudo docker build -f Dockerfile_2.0 -t mramshaw4docs/python-django-gunicorn:2.0 .\n\nAvailable from DockerHub [here](https://hub.docker.com/r/mramshaw4docs/python-django-gunicorn/).\n\nAnd check versions:\n\n    $ docker run --rm -it mramshaw4docs/python-django-gunicorn:2.0 /bin/sh\n    /usr/src/app # python --version\n    Python 2.7.13\n    /usr/src/app # python -m django --version\n    1.11.10\n    /usr/src/app # gunicorn --version\n    gunicorn (version 19.7.1)\n    /usr/src/app # pip list --format=freeze | grep psycopg2-binary\n    /usr/src/app # pip list --format=freeze | grep psycopg2\n    psycopg2 (2.7.4)\n    /usr/src/app # exit\n    $\n\n[Hmm, that should really have been Python 3, can fix later.]\n\n## Replicated Django\n\nLets change `polls.yaml` for our __mramshaw4docs/python-django-gunicorn:2.0__ version and 3 replicas. Also, add:\n\n      sessionAffinity: ClientIP\n      sessionAffinityConfig:\n        clientIP:\n          timeoutSeconds: 10800\n\nThen save it as 'polls-postgres.yaml' and run:\n\n    $ kubectl create -f ./polls-postgres.yaml\n\n# Run our Dockerized app (2.0)\n\nFinally, lets try out our latest `polls` service:\n\n    $ minikube service polls\n\n[This will pop open a browser.]\n\nTo simply see the service address:\n\n    $ minikube service --url polls\n    http://192.168.99.100:30919\n    $\n\n[This can be opened in a browser.]\n\n# Final teardown\n\nAnd finally, close everything down:\n\n    $ kubectl delete -f ./polls-postgres.yaml\n    service \"polls\" deleted\n    deployment \"polls\" deleted\n    $ kubectl delete -f ./postgres.yaml\n    service \"polls-backend\" deleted\n    deployment \"polls-backend\" deleted\n    $ minikube stop\n    Stopping local Kubernetes cluster...\n    Machine stopped.\n    $\n\n# Versions\n\n* Django 1.11.10\n* Docker 17.12.0-ce (Client and Server)\n* gunicorn 19.7.1\n* kubectl (Client: v1.8.6, Server: v1.9.0)\n* Kubernetes v1.9.0\n* minikube v0.25.0\n* psycopg2 2.7.4\n* Python - various\n* PostgreSQL 10.2\n\n# To Do\n\n- [x] Add a Snyk.io vulnerability scan badge\n- [x] Upgrade to most recent __minikube__ (v0.25.0)\n- [x] Upgrade to most recent __kubectl__ (v1.8.6 - client, v1.9.0 - server)\n- [x] Verify `polls` app (written and tested with Python __2.7.12__) works with the latest Python (__3.6.4__)\n- [x] Experiment with `sticky sessions`\n- [x] Experiment with various `service` types [__minikube__ does not support __LoadBalancer__ services]\n- [x] Update `pip` instructions for latest `pip` (format: __freeze__ replaces __legacy__)\n- [ ] Add Kubernetes health checks\n- [ ] Handle Django static content (CSS, etc.)\n- [ ] Harden Django/gunicorn configuration\n- [ ] Harden everything else with non-default passwords and credentials\n- [ ] Persist the back-end database\n- [ ] Replicate the back-end database (probably logically using WAL)\n- [ ] Investigate adding a cache (probably `redis` rather than `memcached` / `pgmemcache`)\n- [ ] Upgrade the 2.0 Django server to a 3.0 Django server (Python3)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmramshaw%2Fcloud_django","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmramshaw%2Fcloud_django","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmramshaw%2Fcloud_django/lists"}