Skip to content

Commit 43416b5

Browse files
committed
Merge branch 'main' into chapter-11-pre-review
2 parents 10cb4e8 + e8abda8 commit 43416b5

File tree

12 files changed

+270
-140
lines changed

12 files changed

+270
-140
lines changed

.git-blame-ignore-revs

Whitespace-only changes.

chapter_09_docker.asciidoc

Lines changed: 142 additions & 70 deletions
Large diffs are not rendered by default.

chapter_10_production_readiness.asciidoc

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[[chapter_10_production_readiness]]
2-
== Making our App Production-Ready
2+
== Making Our App Production-Ready
33

44
.A Note for Early Release Readers
55
****
@@ -47,10 +47,13 @@ Instead, we'll use the popular Gunicorn Python/WSGI server.
4747
In addition, several options in _settings.py_ are currently unacceptable.
4848
`DEBUG=True`, is strongly recommended against for production,
4949
we'll want to set a unique `SECRET_KEY`,
50-
and as we'll see, other things will come up.
50+
and, as we'll see, other things will come up.
5151

5252
Let's go through and see if we can fix things one by one.
5353

54+
// SEBASTIAN: How about linking to Django's documentation
55+
// https://docs.djangoproject.com/en/5.0/howto/deployment/
56+
// somewhere later in the chapter for curious readers?
5457

5558
=== Switching to Gunicorn
5659

@@ -88,6 +91,8 @@ CMD gunicorn --bind :8888 superlists.wsgi:application <2>
8891
which is usually a function called `application`.
8992
Django provides one in _superlists/wsgi.py_.
9093

94+
// TODO; this is a good place to switch to using array syntax for CMD maybe?
95+
9196
As in the previous chapter, we can use the `docker build && docker run`
9297
pattern to try out our changes by rebuilding and rerunning our container:
9398

@@ -99,6 +104,12 @@ $ *docker build -t superlists . && docker run \
99104
-it superlists*
100105
----
101106

107+
// DAVID: Incidentally I got the following error:
108+
// Bind for 0.0.0.0:8888 failed: port is already allocated.
109+
// Turned out the previous container was still running,
110+
// I just used the docker kill process you taught me about earlier.
111+
// Not sure if it's worth including that here, possibly clutter?
112+
102113

103114
==== The FTs catch a problem with static files
104115

@@ -187,6 +198,14 @@ $ *docker build -t superlists . && docker run \
187198
And if you take another manual look at your site, things should look much healthier.
188199
Let's rerun our FTs to confirm:
189200

201+
// DAVID: Incidentally as per your suggestion Harry I have just been skipping
202+
// the requirements.txt and instead just amending the pip install in the Dockerfile.
203+
// If we do that, however, we'll need to make sure readers rebuild the image each time
204+
// we add a requirement - such as at this point.
205+
206+
207+
// JAN: That doesn't work until you install whitenoise to venv on local machine
208+
190209
[role="small-code"]
191210
[subs="specialcharacters,macros"]
192211
----
@@ -201,7 +220,13 @@ Ran 3 tests in 10.718s
201220
OK
202221
----
203222

204-
Phew. Let's commit that
223+
// DAVID: I got
224+
// UserWarning: No directory at: /Users/david.seddon/Documents/reviewing/goat-book/src/static/
225+
// mw_instance = middleware(adapted_handler)
226+
// I think we need to move the static folder into src too, in the previous chapter.
227+
228+
229+
Phew. Let's commit that:
205230

206231
[subs="specialcharacters,quotes"]
207232
----
@@ -229,6 +254,8 @@ I'm sure you'll come across many others.]
229254

230255
The `pip freeze` command will show us everything that's installed in our virtualenv at the moment:
231256

257+
// JAN: This is true only if one installs the libraries locally. So far when following the book we installed them only inside Docker image
258+
232259
[subs="specialcharacters,quotes"]
233260
----
234261
$ *pip freeze*
@@ -285,11 +312,18 @@ whitenoise==6.6.0
285312
====
286313

287314

315+
That's a good first cut, let's commit it:
316+
317+
// SEBASTIAN I am not very fond of freezing just "top-level" dependencies of the project,
318+
// . This is not guaranteeing the reproducible builds and I failed to build & deploy because of that more than once.
319+
// But I understand it's a trade-off so the reader does get bogged down.
320+
// TODO: add a note about this and a link to some more reading
321+
288322
----
289-
# that's a good first cut, let's commit it:
290323
$ *git add requirements.txt*
291324
$ *git commit -m "Add a requirements.txt with Django, gunicorn and whitenoise"*
292325
----
326+
293327
You may be wondering why we didn't add our other dependency,
294328
Selenium, to our requirements,
295329
or why we didn't just add _all_ the dependencies,
@@ -349,6 +383,15 @@ TIP: Forgetting the `-r` and running `pip install requirements.txt`
349383
(which is thankfully much more helpful than it used to be).
350384
It's a mistake I still make, _all the time_.
351385

386+
387+
// CSANAD: It's fine for now, but I would definitely put the requirements under
388+
// /tmp and then `rm` it after `pip install`. Also, using a non-privileged
389+
// user is important, something like:
390+
// `adduser --no-create-home --disabled-password todoapp`
391+
// and then setting the user in the Dockerfile with `USER todoapp`.
392+
// But we can cover this in a later chapter (the next one looks like a good fit,
393+
// since it's related to the app being production ready).
394+
352395
Let's do a build & run & test to check everything still works:
353396

354397
[subs="specialcharacters,quotes"]
@@ -387,12 +430,13 @@ See http://www.clearlytech.com/2014/01/04/12-factor-apps-plain-english/[
387430
Another common way of handling this
388431
is to have different versions of _settings.py_ for dev and prod.
389432
That can work fine too, but it can get confusing to manage.
390-
Environment variables also have the advantage of working for non-Django stuff too...
433+
Environment variables also have the advantage of working for non-Django stuff too.
391434
]
392435

393436

394437
==== Setting DEBUG=True and SECRET_KEY
395438

439+
//RITA: What do you mean by "this"? Please add a word or two for context.
396440
There's lots of ways you might do this.
397441
Here's what I propose; it may seem a little fiddly,
398442
but I'll provide a little justification for each choice.
@@ -437,7 +481,7 @@ TIP: Better to fail hard than allow a typo in an environment variable name to
437481

438482
==== Setting environment variables inside the Dockerfile
439483

440-
Now let's set that environment variable in our Dockerfile using then `ENV` directive:
484+
Now let's set that environment variable in our Dockerfile using the `ENV` directive:
441485

442486
[role="sourcecode"]
443487
.Dockerfile (ch10l007)
@@ -470,7 +514,7 @@ $ pass:specialcharacters,quotes[*docker build -t superlists . && docker run \
470514
KeyError: 'DJANGO_SECRET_KEY'
471515
----
472516

473-
Ooops, and I forgot to set said secret key env var,
517+
Oops. I forgot to set said secret key env var,
474518
mere seconds after having dreamt it up!
475519

476520

@@ -505,7 +549,7 @@ AssertionError: 'To-Do' not found in 'Bad Request (400)'
505549

506550
==== ALLOWED_HOSTS is Required When Debug Mode is Turned Off
507551

508-
Not quite! Let's take a look manually: <<django-400-error>>.
552+
It's not quite working yet! Let's take a look manually: <<django-400-error>>.
509553

510554
[[django-400-error]]
511555
.An ugly 400 error
@@ -534,7 +578,7 @@ image::images/search-results-400-bad-request.png["Duckduckgo search results with
534578
`ALLOWED_HOSTS` is a security setting
535579
designed to reject requests that are likely to be forged, broken or malicious
536580
because they don't appear to be asking for your site
537-
(HTTP request contain the address they were intended for in a header called "Host").
581+
(HTTP requests contain the address they were intended for in a header called "Host").
538582

539583
By default, when DEBUG=True, `ALLOWED_HOSTS` effectively allows _localhost_,
540584
our own machine, so that's why it was working OK until now.
@@ -563,7 +607,7 @@ else:
563607
====
564608

565609
This is a setting that we want to change,
566-
depending on whether our docker image is running locally,
610+
depending on whether our Docker image is running locally,
567611
or on a server, so we'll use the `-e` flag again:
568612

569613

@@ -581,7 +625,7 @@ $ *docker build -t superlists . && docker run \
581625
==== Collectstatic is Required when Debug is Turned Off
582626

583627
An FT run (or just looking at the site) reveals that we've had a regression
584-
in our static files.
628+
in our static files:
585629

586630
[role="small-code"]
587631
[subs="specialcharacters,macros"]
@@ -615,6 +659,13 @@ CMD gunicorn --bind :8888 superlists.wsgi:application
615659
----
616660
====
617661

662+
// DAVID: It would be nice to explain the difference between RUN and CMD.
663+
664+
// DAVID: Interestingly when I did this I put the RUN directive after the ENV
665+
// directive, which led to a KeyError: 'DJANGO_SECRET_KEY' which foxed me for a bit.
666+
// Might be worth calling out that we're running collectstatic in debug mode.
667+
668+
618669
// TODO: gitignore src/static
619670

620671
Well, it was fiddly, but that should get us to passing tests!
@@ -631,6 +682,9 @@ OK
631682

632683
We're nearly ready to ship to production!
633684

685+
// DAVID: It might be worth pointing out what Whitenoise is actually doing.
686+
// From what I understand, we're still using Django to serve static files.
687+
634688
Let's quickly adjust our gitignore, since the static folder is in a new place:
635689

636690
//0010
@@ -645,6 +699,21 @@ $ *git commit -am "Add collectstatic to dockerfile, and new location to gitignor
645699
----
646700

647701

702+
=== Switching to a nonroot user
703+
704+
TODO: WIP, this is definitely a good idea for security, needs writing up.
705+
706+
Dockerfile should gain some lines a bit like this:
707+
708+
.Dockerfile (ch10l0XX)
709+
====
710+
[source,dockerfile]
711+
----
712+
RUN addgroup --system nonroot && adduser --system --group nonroot
713+
714+
USER nonroot
715+
----
716+
648717
649718
650719
=== Configuring logging
@@ -666,7 +735,6 @@ $ *rm src/db.sqlite3*
666735
$ *touch src/db.sqlite3* # otherwise the --mount type=bind will complain
667736
----
668737
669-
670738
Now if you run the tests, you'll see they fail;
671739
672740
[role="small-code"]
@@ -680,6 +748,10 @@ selenium.common.exceptions.NoSuchElementException: Message: Unable to locate
680748
element: [id="id_list_table"]; [...]
681749
----
682750
751+
// DAVID: Got me thinking, I'm not always clear when I need to rebuild the image.
752+
// I would have thought I might need to do it here, but I didn't. Might be worth
753+
// explaining in the previous chapter when we do.
754+
683755
And you might spot in the browser that we just see a minimal error page,
684756
with no debug info (try it manually if you like):
685757
@@ -715,7 +787,7 @@ I mean, yes, separating the concepts of handlers and loggers and filters,
715787
and making it all configurable in a nested hierarchy is all well and good
716788
and covers every possible use case,
717789
but sometimes you just wanna say "just print stuff to stdout pls",
718-
and you wish that configuring the simplest thing was a little easier].
790+
and you wish that configuring the simplest thing was a little easier.].
719791
720792
Here's pretty much the simplest possible logging config
721793
which just prints everything to the console (ie standard out).
@@ -743,7 +815,6 @@ Rebuild and restart our container,
743815
try the FT again (or submitting a new list item manually)
744816
and we now should see a clear error message:
745817
746-
747818
----
748819
Internal Server Error: /lists/new
749820
Traceback (most recent call last):
@@ -759,7 +830,7 @@ Traceback (most recent call last):
759830
django.db.utils.OperationalError: no such table: lists_list
760831
----
761832
762-
Re-create the datase with `./src/manage.py migrate` and we'll be back to a working state.
833+
Re-create the database with `./src/manage.py migrate` and we'll be back to a working state.
763834
764835
Don't forget to commit our changes to _settings.py_,
765836
and I think we can call it job done!
@@ -768,6 +839,8 @@ when considering production-readiness,
768839
we've worked in small steps and used our tests all the way along,
769840
and we're now ready to deploy our container to a real server!
770841
842+
Find out how, in our next exciting installment...
843+
771844
772845
[role="pagebreak-before less_space"]
773846
.Production-Readiness Config

chapter_11_ansible.asciidoc

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ It's a common tool in Ansible scripts, and the syntax is very similar to Django'
702702
Here's what our template for the env file will looks like:
703703

704704
[role="sourcecode"]
705-
.infra/env.j2 (ch11l003)
705+
.infra/env.j2 (ch11l004)
706706
====
707707
[source,python]
708708
----
@@ -716,7 +716,7 @@ And here's how we use it in the provisioning script:
716716

717717

718718
[role="sourcecode small-code"]
719-
.infra/ansible-provision.yaml (ch11l004)
719+
.infra/ansible-provision.yaml (ch11l005)
720720
====
721721
[source,yaml]
722722
----
@@ -935,7 +935,7 @@ We want to map port 8888 inside the container as port 80 (the default web/http p
935935
on the server:
936936

937937
[role="sourcecode"]
938-
.infra/ansible-provision.yaml (ch11l005)
938+
.infra/ansible-provision.yaml (ch11l006)
939939
====
940940
[source,yaml]
941941
----
@@ -1010,7 +1010,7 @@ Here's how
10101010
// little incomplete.
10111011

10121012
[role="sourcecode"]
1013-
.infra/ansible-provision.yaml (ch11l006)
1013+
.infra/ansible-provision.yaml (ch11l007)
10141014
====
10151015
[source,python]
10161016
----
@@ -1084,8 +1084,7 @@ and reloads it automatically if it crashes.
10841084
*******************************************************************************
10851085
10861086
A few more places to look and things to try, now that we've introduced
1087-
Podman and Systemd into the mix, should things not go according to plan:
1088-
// CSANAD: we did not mention Podman or Systemd in this chapter
1087+
Docker into the mix, should things not go according to plan:
10891088
10901089
- You can check the Container logs using
10911090
`docker logs superlists`.

pre-requisite-installations.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ image::images/git_windows_installer_choose_editor.png["Screenshot of Git install
162162
.Add Python to the system path from the installer
163163
image::images/python_install_add_to_path.png["Screenshot of python installer"]
164164
165+
// TODO: update screenshot above for 3.12
166+
165167
The test for all this is that you should be able to go to a Git-Bash command prompt
166168
and just run `python` or `pip` from any folder.
167169

preface.asciidoc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,11 @@ either via www.obeythetestinggoat.com, or via the O'Reilly Learning site.
4343
Congratulations!
4444
4545
At the time of writing, the whole of _Part 1_ of the book (the first 7 chapters),
46-
and the first chapter in Part 2 (chapter 8)
47-
have been updated for the third edition.
48-
They’ve been upgraded to Python 3.11, Django 4,
49-
and the text has been comprehensively reviewed.
46+
and the bulk of Part 2 (chapter 8-14) have been updated for the third edition.
47+
They’ve been upgraded to Python 3.12, Django 4,
48+
and the text up to chapter 12 has been comprehensively reviewed.
5049
51-
_Chapters 9 and above are still on Python 3.7 / Django 1.x._
50+
_Chapters 15 and above are still on Python 3.7 / Django 1.x._
5251
5352
If you're following through the code examples in rest of the book,
5453
you have two choices:

source/chapter_09_docker/superlists

Submodule superlists updated 1 file

source/push-back.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ CHAP=$1
55

66
cd "$CHAP/superlists"
77
git push --force-with-lease local "$CHAP"
8-
# git fpush origin $CHAP
8+
git push --force-with-lease origin "$CHAP"
99

1010
cd ../..

0 commit comments

Comments
 (0)