To fix the current issue with our application, we need to have a mechanism that allows us to run database migrations to create the required database schema and tables. This must also happen on each application update, however this should only occur once per application update. For example, if you had multiple Elastic Beanstalk instances, you do not want migrations to run on each instance. Instead, you want migrations to run only once for each deployment.
The container_commands key that you were introduced to in the previous section includes a useful property called leader_only, which configures Elastic Beanstalk to only run the specified command on the leader instance. This is the first instance that becomes available to deploy to. We can therefore add a new directive to the .ebextensions/init.config file in the todobackend-aws/eb folder that will run migrations only once per application deployment:
commands:
01_add_ec2_user_to_docker_group:
command: usermod -aG docker ec2-user
ignoreErrors: true
02_docker_volumes:
command: |
mkdir -p /tmp/public
mkdir -p /tmp/init
chown -R 1000:1000 /tmp/public
chown -R 1000:1000 /tmp/init
container_commands:
01_rds_settings:
command: |
config=/opt/elasticbeanstalk/deploy/configuration/containerconfiguration
environment=/tmp/init/environment
echo "MYSQL_HOST=$(jq '.plugins.rds.env.RDS_HOSTNAME' -r $config)" >> $environment
echo "MYSQL_USER=$(jq '.plugins.rds.env.RDS_USERNAME' -r $config)" >> $environment
echo "MYSQL_PASSWORD=$(jq '.plugins.rds.env.RDS_PASSWORD' -r $config)" >> $environment
echo "MYSQL_DATABASE=$(jq '.plugins.rds.env.RDS_DB_NAME' -r $config)" >> $environment
chown -R 1000:1000 $environment
02_migrate:
command: |
echo "python3 manage.py migrate --no-input" >> /tmp/init/commands
chown -R 1000:1000 /tmp/init/commands
leader_only: true
Here, we write the python3 manage.py migrate --no-input command to the /tmp/init/commands file, which will be exposed to the application container at the location /init/commands. This, of course, requires us to now modify the entrypoint script in the todobackend repository to look for such a file and execute the commands contained within it, as follows:
#!/bin/bash
set -e -o pipefail
# Inject AWS Secrets Manager Secrets
# Read space delimited list of secret names from SECRETS environment variable
echo "Processing secrets [${SECRETS}]..."
read -r -a secrets <<< "$SECRETS"
for secret in "${secrets[@]}"
do
vars=$(aws secretsmanager get-secret-value --secret-id $secret \
--query SecretString --output text \
| jq -r 'to_entries[] | "export \(.key)='\''\(.value)'\''"')
eval $vars
done
# Inject runtime environment variables
if [ -f /init/environment ]
then
echo "Processing environment variables from /init/environment..."
export $(cat /init/environment | xargs)
fi
# Inject runtime init commands
if [ -f /init/commands ]
then
echo "Processing commands from /init/commands..."
source /init/commands
fi
# Run application
exec "$@"
Here, we add a new test expression that checks for the existence of the /init/commands file, and if this file exists we use the source command to execute each command contained within the file. Because this file will only be written on the leader Elastic Beanstalk instance, the entrypoint script will only invoke these commands once per deployment.
At this point, you need to rebuild the todobackend Docker image by running the make login, make test, make release, and make publish commands, after which you can deploy your Elastic Beanstalk changes by running the eb deploy command from the todobackend-aws/eb directory. Once this has completed successfully, if you SSH to your Elastic Beanstalk instance and review the logs of the current active todobackend application container, you should see that the database migrations were executed when the container was started:
> docker ps --format "{{.ID}}: {{.Image}}"
45b8cdac0c92: 385605022855.dkr.ecr.us-east-1.amazonaws.com/docker-in-aws/todobackend
45bf3329a686: amazon/amazon-ecs-agent:latest
> docker logs 45b8cdac0c92
Processing secrets []...
Processing environment variables from /init/environment...
Processing commands from /init/commands...
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, todo
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying sessions.0001_initial... OK
Applying todo.0001_initial... OK
[uwsgi-static] added check for /public
*** Starting uWSGI 2.0.17 (64bit) on [Sun Jul 15 11:18:06 2018] ***
If you now browse to the application URL, you should find that the application is fully functional, and you have successfully deployed a Docker application to Elastic Beanstalk.
Before we wrap up this chapter, you should restore the MFA configuration you temporarily disabled earlier in this chapter by adding your user account back to the Users group:
> aws iam add-user-to-group --user-name justin.menga --group-name Users
And then re-enable the mfa_serial line within the docker-in-aws profile inside your local ~/.aws/config file:
[profile docker-in-aws]
source_profile = docker-in-aws
role_arn = arn:aws:iam::385605022855:role/admin
role_session_name=justin.menga
region = us-east-1
mfa_serial = arn:aws:iam::385605022855:mfa/justin.menga
You can also delete your Elastic Beanstalk environment by browsing to the main Elastic Beanstalk dashboard and clicking the Actions | Delete application button next to the todobackend application. This will delete the CloudFormation stack that was created by the Elastic Beanstalk environment, which includes the application load balancer, RDS database instance, and EC2 instances.