Everything can be done on your local machine (your "test server"). Anything touching production is simulated here. Do not look at solutions.md before submitting.
Setup:
cd ~/dev/ticketflow && source .venv/bin/activateExercise 1 — Anatomy of your processes
- Start Gunicorn:
gunicorn config.wsgi --bind 127.0.0.1:8000 --workers 2in one terminal. - In another:
ps -o pid,ppid,rss,cmd -C gunicorn. Identify the parent process (arbiter) and the 2 workers. How do you tell them apart from the command? kill -TERM <a_worker_pid>: what happens in Gunicorn's terminal? Does the service keep answeringcurl? (hint: the arbiter replaces it).kill -9 <a_worker_pid>and repeat: what difference do you observe in the logs?
Exercise 2 — Ports and load
ss -tlnp | grep -E '8000|5432|6379': which processes listen on each port?- Generate load and watch:
top(orhtop) while runningfor i in $(seq 200); do curl -s -o /dev/null http://127.0.0.1:8000/api/health/ & done. How much CPU do the workers consume? And thecurlprocesses? uptimeafter the test: how do you read the three numbers on YOUR machine (find the core count withnproc)?
Exercise 3 — The log pipeline
- Generate a test log with 100 lines (or use Gunicorn's error.log).
- Run and explain each link:
cat access.log | awk '{print $9}' | sort | uniq -c | sort -rn(what is $9 in an Nginx/Gunicorn access log? which question does it answer?)
- Write a pipeline showing the top 5 slowest routes (the longest ones). Hint: if your log has the duration, use
sort -kandhead; if not, add the duration to the log format first.
Exercise 4 — Real permissions
- Create a shell-less user:
sudo useradd -r -s /usr/sbin/nologin app_test. - Create a file with a simulated secret and give it 644:
echo "SECRET=x" > /tmp/secret.env && chmod 644 /tmp/secret.env. Withsudo -u app_test cat /tmp/secret.env, can it read the file? Why? - Switch to
chmod 640and set the app user's group: who can read it now? And with 600? - Write the rule you will apply to your real
.env(and run it on your project:chmod 600.env).
Exercise 5 — The well-made.env
- In your project, create
env.examplewith every namesettings.pyuses (no real values). - Verify your real
.envis ignored by Git:git check-ignore -v.env(if it isn't, fix it NOW and confirm withgit statusthat it doesn't appear). - Search your history for leaked secrets:
git log -p | grep -iE "secret|password|key" | head. If something shows up, note it to rotate it (the security lesson systematizes this).
Exercise 6 — Local SSH (lab)
- Generate (if you don't have one) your ed25519 key and test it against localhost:
ssh-copy-id your_user@localhostand thenssh your_user@localhost "hostname". Did it get in without a password? - Create
~/.ssh/configwith alocaltesthost pointing to localhost and repeat the command withssh localtest hostname. - Simulated tunnel:
ssh -L 5433:localhost:5432 your_user@localhostin one terminal; in another, check the port:ss -tlnp | grep 5433. Who is listening? What would this allow you with a real remote DB?
Exercise 7 — Your first operations script
- Write
healthcheck.sh(the one from the lesson) and make it executable (chmod +x). Test with Gunicorn down and up. - Add the broker check: if Redis does not answer (
redis-cli ping), the script warns but does not fail (exit 0 with a warning); if the API does not answer, it does fail. Justify the difference in severity. - Have it run every minute in the background with
watch -n 60./healthcheck.shfor a while. How would you do it "for real" in production? (hint: systemd timer or cron — note it down, we formalize it in Lesson 31).
Submit
Paste the trimmed outputs and your healthcheck.sh in the chat. Lesson 05 closes and we move to Lesson 06 — Professional Git: from the daily commit to the team flow with branches, rebase and conflicts.