Skip to content

Commit c934218

Browse files
committed
Merge remote-tracking branch 'csanad/review--chapter-10-production-readiness-rev2'
2 parents 48876ad + 40b5792 commit c934218

File tree

3 files changed

+90
-7
lines changed

3 files changed

+90
-7
lines changed

chapter_10_production_readiness.asciidoc

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,25 @@ so that people can access it using a regular URL.
4141

4242
Perhaps more importantly, we shouldn't use the Django dev server for production;
4343
it's not designed for real-life workloads.
44-
Instead, we'll use the popular Gunicorn Python/WSGI server.
44+
Instead, we'll use the popular Gunicorn Python WSGI HTTP server.
45+
46+
NOTE: Django's `runserver` is built and optimised for local development and debugging.
47+
It's designed to handle one user at a time,
48+
it handles automatic reloading upon saving of the source code,
49+
but it isn't optimised for performance,
50+
nor has it been hardened against security vulnerabilities.
4551

4652
((("DEBUG settings")))
4753
In addition, several options in _settings.py_ are currently unacceptable.
48-
`DEBUG=True`, is strongly recommended against for production,
54+
`DEBUG=True`, is strongly discouraged for production,
4955
we'll want to set a unique `SECRET_KEY`,
5056
and, as we'll see, other things will come up.
5157

58+
NOTE: DEBUG=True is considered a security risk,
59+
because the django debug page will display sensitive information like
60+
the values of variables, and most of the settings in settings.py.
61+
62+
5263
Let's go through and see if we can fix things one by one.
5364

5465
// SEBASTIAN: How about linking to Django's documentation
@@ -68,6 +79,47 @@ which I guess is what you'd want next if you already had a pony...
6879
We'll need to first install Gunicorn into our container,
6980
and then use it instead of `runserver`:
7081

82+
//001
83+
84+
[subs="specialcharacters,quotes"]
85+
----
86+
$ *pip install gunicorn*
87+
Collecting gunicorn
88+
[...]
89+
Successfully installed gunicorn-21.2.0
90+
----
91+
92+
[[what-is-wsgi]]
93+
.What is WSGI?
94+
*******************************************************************************
95+
Gunicorn is a WSGI (usually pronounced as whizgee) HTTP server.
96+
Let's go through what this means:
97+
98+
* An HTTP server handles incoming HTTP requests and sends back responses to the
99+
clients.
100+
101+
* WSGI, the Web Server Gateway Interface, is a set of rules defining how an HTTP
102+
server (which can be Gunicorn itself, or Nginx, Apache, etc) and a Python
103+
program should communicate.
104+
105+
So, Gunicorn is an HTTP server that knows and uses the WSGI to "talk" to Python programs.
106+
And these Python programs are likewise expected to work in a specific way.
107+
You can check out a simple example https://gunicorn.org/[on Gunicorn's website] under "Installation".
108+
109+
https://peps.python.org/pep-3333[If you are interested, you can read the entire
110+
specification here].
111+
112+
// CSANAD: I felt like some explanation regarding what Gunicorn and WSGI are,
113+
// was missing.
114+
115+
*******************************************************************************
116+
117+
118+
Gunicorn will need to know a path to a "WSGI server" (see <<what-is-wsgi>>),
119+
which is usually a function called `application`.
120+
Django provides one in 'superlists/wsgi.py'.
121+
Let's change the command our image runs:
122+
71123
[role="sourcecode"]
72124
.Dockerfile (ch10l001)
73125
====
@@ -83,6 +135,9 @@ WORKDIR /src
83135
CMD gunicorn --bind :8888 superlists.wsgi:application <2>
84136
----
85137
====
138+
// CSANAD: shouldn't we try it out before adding this to the Dockerfile?
139+
// `cd src` and then
140+
// `gunicorn --bind :8888 superlists.wsgi:application`
86141

87142
<1> Installation is a standard pip install.
88143

@@ -172,6 +227,11 @@ MIDDLEWARE = [
172227
173228
----
174229
====
230+
// CSANAD: I would add a few thoughts on the significance of the order of
231+
// middlewares.
232+
233+
And if you take another manual look at your site after rebuilding the
234+
container, things should look much healthier.
175235

176236
And then we need to add it to our pip installs in the Dockerfile:
177237

@@ -437,7 +497,8 @@ Environment variables also have the advantage of working for non-Django stuff to
437497
==== Setting DEBUG=True and SECRET_KEY
438498

439499
//RITA: What do you mean by "this"? Please add a word or two for context.
440-
There's lots of ways you might do this.
500+
There are lots of ways you might do this.
501+
441502
Here's what I propose; it may seem a little fiddly,
442503
but I'll provide a little justification for each choice.
443504
Let them be an inspiration (but not a template) for your own choices!
@@ -459,6 +520,10 @@ else:
459520
SECRET_KEY = "insecure-key-for-dev"
460521
----
461522
====
523+
// CSANAD: I think variable names like "something_false" are confusing, since
524+
// we need to set something to true so that they mean false.
525+
// How about `DJANGO_ENV_PRODUCTION` or something similar?
526+
// TODO yes i like this
462527

463528
<1> We say we'll use an environment variable called `DJANGO_DEBUG_FALSE`
464529
to switch debug mode off, and in effect require production settings
@@ -479,6 +544,14 @@ against accidentally forgetting to set one.
479544
TIP: Better to fail hard than allow a typo in an environment variable name to
480545
leave you running with insecure settings.
481546

547+
// CSANAD: I think it would worth pointing out the development environment
548+
// does not use Docker, launching the dev server should be done from
549+
// the reader's host system. I think this isn't immediately obvious, e.g. I
550+
// thought all along that from now on we would only run the server from Docker.
551+
// If we end up making a TIP or similar about it, I think we should also mention
552+
// in a development environment relying on containerization, programmers usually
553+
// mount the whole /src minimizing the time-consuming rebuilding of their images.
554+
482555
==== Setting environment variables inside the Dockerfile
483556

484557
Now let's set that environment variable in our Dockerfile using the `ENV` directive:
@@ -630,6 +703,10 @@ $ *docker build -t superlists . && docker run \
630703
An FT run (or just looking at the site) reveals that we've had a regression
631704
in our static files:
632705

706+
// CSANAD: worth mentioning there was another clue. in the logs, once Gunicorn
707+
// starts running you can see:
708+
// `UserWarning: No directory at: /src/static/`
709+
633710
[role="small-code"]
634711
[subs="specialcharacters,macros"]
635712
----
@@ -661,6 +738,7 @@ ENV DJANGO_DEBUG_FALSE=1
661738
CMD gunicorn --bind :8888 superlists.wsgi:application
662739
----
663740
====
741+
// CSANAD: I think it would be important to use a non-privileged user in Docker
664742

665743
// DAVID: It would be nice to explain the difference between RUN and CMD.
666744

@@ -701,6 +779,12 @@ $ *git status*
701779
$ *git commit -am "Add collectstatic to dockerfile, and new location to gitignore"*
702780
----
703781

782+
// CSANAD: now would be a good time to check our changes and notice git marked
783+
// src/static as unstaged, so we should update .gitignore accordingly:
784+
// from `static` to `src/static`.
785+
// Or we could add this step to Chapter 09 at "Move all our code into a src
786+
// folder"
787+
704788

705789
=== Switching to a nonroot user
706790

@@ -718,7 +802,6 @@ USER nonroot
718802
----
719803
720804
721-
722805
=== Configuring logging
723806
724807
One last thing we'll want to do is make sure that we can get logs out of our server.
@@ -780,7 +863,7 @@ but it's more of shock to see that they are no longer appearing in the terminal
780863
If you're like me you might find yourself wondering if we really _did_ see them earlier
781864
and starting to doubt your own sanity.
782865
But the explanation is that Django's
783-
https://docs.djangoproject.com/en/5.0/ref/logging/#default-logging-configuration[default logging configuration]
866+
https://docs.djangoproject.com/en/4.2/ref/logging/#default-logging-configuration[default logging configuration]
784867
changes when DEBUG is turned off:
785868
786869
This means we need to interact with the standard library's `logging` module,
@@ -793,7 +876,7 @@ but sometimes you just wanna say "just print stuff to stdout pls",
793876
and you wish that configuring the simplest thing was a little easier.].
794877
795878
Here's pretty much the simplest possible logging config
796-
which just prints everything to the console (ie standard out).
879+
which just prints everything to the console (i.e. standard out).
797880
798881
799882
[role="sourcecode"]
@@ -835,7 +918,7 @@ django.db.utils.OperationalError: no such table: lists_list
835918
836919
Re-create the database with `./src/manage.py migrate` and we'll be back to a working state.
837920
838-
Don't forget to commit our changes to _settings.py_,
921+
Don't forget to commit our changes to _settings.py_ and _Dockerfile_,
839922
and I think we can call it job done!
840923
We've at least touched on many or most of the things you might need to think about
841924
when considering production-readiness,

images/twp2_1001.png

-30.4 KB
Loading

images/twp2_1002.png

-18.1 KB
Loading

0 commit comments

Comments
 (0)