-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: use PostgreSQL for the database backend #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
d426a89
to
9346872
Compare
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
- configure connection settings from the environment - add ability to configure a root CA certificate(s) from the server to verify the client connection
- creates an admin connection - resets database users and databases - validates the database configuration - checks if a user/database exists - creates a user/database - checks schema permissions - runs migrations - ensures a superuser exists
461bd51
to
824a263
Compare
since ensure_db implements the same functionality, the .sh commands are cleaned up to avoid redundancy. in the pytest workflow, running migrations is not needed since tests will migrate the database, but we still need to collect static files and generate the static files manifest so that the workflow does not fail.
introduce a healthcheck for the postgres service to ensure the web and pgweb services do not start until the postgres server is ready. this ensures we don't get an Admin connection to PostgreSQL fail for web and an Error: connection refused error for pgweb when there is no postgres service already running when web or pgweb are started. This usually happens when the containers are first created.
824a263
to
efaa9a6
Compare
- adds an Aurora Serverless storage to the copilot web service - udpates the Dockerfile to include certs for PostgreSQL verify-full (uses the AWS Global RDS CA Bundle in case us-west-2 or us-east-1 is used) - supports database creation for local and dev environments (the dev environment uses the copilot-injected POSTGRESWEB_SECRET environment variable to connect to the database) - updates the start_aws.sh script
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this is looking really good! I was able to run everything locally without any issues.
Some small changes requested. Few questions/notes as well.
compose.yml
Outdated
- pgdata:/var/lib/postgresql/data | ||
|
||
pgweb: | ||
container_name: pgweb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know you grabbed this as-is from the CDRC work, but I realized there's a conflict.
If both projects have this container_name
attribute, then the second one fails to come up:
$ docker compose up pgweb
[+] Running 2/2
✔ Container pems-postgres-1 Running 0.0s
✘ Container pgweb Error response from daemon: Conflict. The container name "/pgweb" is already in use by contain... 0.0s
Error response from daemon: Conflict. The container name "/pgweb" is already in use by container "55c3ddad7fe81c74fbbc944894685f33bdf5041399c851e8c6ab62f94ed0f0f6". You have to remove (or rename) that container to be able to reuse that name.
If I remove this line, it comes up just fine. I'll make a ticket to remove it from CDRC as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for noticing this! Removed it in 332fb4a
@@ -0,0 +1,309 @@ | |||
import os |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should definitely find a way to share this command between projects! This is good for now 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure! The tricky thing that I am seeing so far is that we may only need a subset of the ensure_db
"underscore functions" and we may need to reorder how they are run in handle()
for AWS copilot to be able to use it. For example, the copilot storage init
command creates the PostgreSQL cluster, the django
database, and a user (automatically named postgres
by copilot but really it is the user that Django's settings.py
uses to interact with the default
database). So we don't need to interact with the postgres
maintenance database or user (in fact, I think the maintenance database may not be reachable unless we change the CloudFormation template). So because of this slightly different setup, the ensure_db
command may need to be refactored to something more general for it to be shared across projects, but I think it's definitely something we should do.
@@ -91,6 +91,9 @@ ENV GUNICORN_CONF "/$USER/run/gunicorn.conf.py" | |||
# overwrite default nginx.conf | |||
COPY appcontainer/nginx.conf /etc/nginx/nginx.conf | |||
|
|||
# copy certs for PostgreSQL verify-full | |||
COPY appcontainer/certs/aws_global_postgres_ca_bundle.pem app/certs/aws_global_postgres_ca_bundle.pem |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noting a small change I made to how the cert bundle is added to the CDRC image:
- I created an environment variable with the location: https://github.com/Office-of-Digital-Services/cdt-ods-disaster-recovery/blob/main/appcontainer/Dockerfile#L102
- The env var is used in the COPY command: https://github.com/Office-of-Digital-Services/cdt-ods-disaster-recovery/blob/main/appcontainer/Dockerfile#L150
- The env var is also used in settings: https://github.com/Office-of-Digital-Services/cdt-ods-disaster-recovery/blob/main/web/settings.py#L162
This ultimately was because of a change related to the Buildkit caching, where I wasn't copying the entire source directory in anymore during Docker build. That caused some cascading issues with the runtime directory being different from e.g. /cdt/app
, and one of the problems was this cert bundle couldn't be found by Django.
This solves it by not making Django have to construct the path, instead it happens at Docker build time and is baked into the image as an env var.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! If it's ok, I'll add this small change to #132 which is about implementing Buildkit caching.
bin/start_aws.sh
Outdated
python manage.py loaddata $DJANGO_DB_FIXTURES | ||
else | ||
echo "No JSON fixtures to load" | ||
fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like start_aws.sh
should work similarly if it calls out to setup.sh
after downloading the fixtures from S3? Rather than repeating all the migration/loading?
Otherwise, I don't see ensure_db
being called in AWS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I struggled with the organization of this shell script for a bit and even though it works, I don't really like the setup because we are not using ensure_db
. But because of how copilot sets up the postgres database (few more details in this comment) I wasn't able to directly reuse ensure_db
. Copilot essentially does a lot of the setup that ensure_db
does (like making the database and the user) and the only things that start_aws.sh
needed to do was download the fixtures, migrate, and load the JSON fixtures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following #131 (comment), 06d8ca9 fixes how Aurora Serverless is set up.
postgreswebDBName: | ||
Type: String | ||
Description: The name of the initial database to be created in the Aurora Serverless v2 cluster. | ||
Default: django |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the postgres
management database also created by default here?
Since the job of our ensure_db
command is to create the Django user/database, this might be interfering no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I think copilot automatically creates the postgres
management database, but we can't reach it, and probably don't need to due to how copilot sets up the postgres server. We only get access to the default
Django database through the user (automatically named postgres
, unfortunately, since it is not the maintenance/admin user) that copilot creates.
So because of how copilot sets up the database, we don't actually use ensure_db
for AWS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this might be the root of these issues.
It seems like this Default
is actually the management database. You should be able to get to it, using the admin user/password, there is no reason you shouldn't be able to. It's your database cluster!
We definitely do not want Django using the postgres
user. This is another reason I think you should be relying on ensure_db
for all Django database setup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
There was a point during setting this up that I was approaching it how you describe it, but the docs seemed to suggest otherwise and I switched direction 😭 . Your explanation makes sense and I just confirmed it by exploring the database cluster so I'll make a few more changes so that we actually use ensure_db
and set it up correctly.
pgweb is used by other projects, and having a fixed container name can lead to conflicts.
pems/settings.py
Outdated
return env | ||
|
||
|
||
def aws_db_credentials(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this small function since its behavior is used in this file and in ensure_db
. If we do have more functions similar to this one, we can make a utils
-type file for runtime environment related functions. But since we only have this one I decided to leave the definition here, although we can move it to somewhere else if it makes more sense.
this commit fixes the original Aurora Serverless setup by setting the initial database that copilot creates to postgres, and using the ensure_db.py script to configure the Django database. - copilot creates an Aurora Serverless storage cluster with an initial database called 'postgres' - the web service manifest defines secrets related to the Django database - to create the Django database, settings.py is updated to set the database environment variables from the single environment variable automatically created by copilot - the start_aws.sh script now uses ensure_db.py - the Aurora Data API is enabled to allow us to interact with the Aurora database by running SQL queries on the AWS console
test_get_object() in TestDistrictView now follows the CBV testing pattern. the Pytest GH workflow is also updated to remove the static files collection step, as client.get() is no longer used in the test; previously, it was necessary to collect static files for the workflow to succeed.
06d8ca9
to
721edc3
Compare
When you get a chance this is ready for a re-review @thekaveman. I tested it locally and by redeploying to AWS ✅
|
Closes #126, closes #138, closes #139
This PR migrates the Django database from SQLite to PostreSQL. Django's
settings.py
file now uses the PostgreSQL back-end and redundant database-related shell scripts are removed.Locally and for some CI workflows, the
ensure_db
command is used to check that the database is configured, and only creates it if needed (i.e. the first time). The command also runs migrations and creates a superuser if needed, to keep all database setup in the same place. Note that in the PostgreSQL server there are 2 databases:POSTGRES_DB
(for administrative and maintenance purposes) andDJANGO_DB
(for the PeMS application). Also, thecompose
file includes a healthcheck for thepostgres
service.web
andpgweb
will wait untilpostgres
has emitted a healthy signal to ensure we don't get anAdmin connection to PostgreSQL fail
error forweb
and aError: connection refused
error forpgweb
when there is nopostgres
service already running whenweb
orpgweb
are started. This usually happens when the containers are first created, such as in thetests-ui
CI workflow.On the Cloud, an Aurora Serverless database is created using
and it is deployed each time the
web
copilot service is deployed. Once the environments are stabilized, the deployment of the database will be tied to the deployment of the environment instead (issue #137 ). The database is accessible from inside the service's container via thePOSTGRESWEB_SECRET
environment variable that holds the name of database and the credentials for accessing it. This environment variable is automatically injected into theweb
service's container and is used by Django'ssettings.py
.Local behavior
Django App container
Compose's
CMD
(bin/setup.sh && exec bin/start.sh
) overrides the Docker file'sCMD
. There are 2 distinct scenarios related to starting up the database:should_reset=true
setup.sh
POSTGRES_DB
as admin, reset (delete) DJANGO_DB users and database, create DJANGO_DB users and database, run migrations, create Django superuser (ensure_db --reset
)start.sh
collectstatic
)nginx
)gunicorn
)should_reset=false
setup.sh
POSTGRES_DB
as admin, create DJANGO_DB users and database if they don't already exist, run migrations, create Django superuser if it doesn't already exist (ensure_db
)start.sh
collectstatic
)nginx
)gunicorn
)Dev container
devcontainer.json
runsbin/setup.sh
when the container is started. Note thatREMOTE_CONTAINERS=true
by default in the dev container soshould_reset=true
inbin/setup.sh
POSTGRES_DB
as admin, reset DJANGO_DB users and database, create DJANGO_DB users and database, run migrations, create Django superuser (ensure_db --reset
)Cloud behavior
Django App container
Dockerfile's
CMD
isbin/start.sh
but theweb
service's manifest replaces it withbin/start_aws.sh
setup.sh
POSTGRES_DB
as admin, create DJANGO_DB users and database if they don't already exist, run migrations, create Django superuser if it doesn't already exist (ensure_db
)start.sh
collectstatic
)nginx
)gunicorn
)Reviewing
Local
App container
docker compose up
. Verify thatweb
andpgweb
wait to start untilpostgres
is ready. Database is created from scratch, ensure the app works in your browser.docker compose down
and deletepems_pgdata
volume if you need to remove the containers to start from scratch next time.Dev container
There are no special instructions for the dev container, just start it as usual and use the debugger to ensure that the app is accessible.
Cloud
The PeMS app, running with an Aurora Serverless storage, is deployed at http://pems-d-publi-tdvc9tnwbm5n-1785238641.us-west-2.elb.amazonaws.com