Skip to content

Instantly share code, notes, and snippets.

@vlntsolo
Last active October 30, 2022 15:39
Show Gist options
  • Save vlntsolo/34261e6026ac0e303c40c6ece9961182 to your computer and use it in GitHub Desktop.
Save vlntsolo/34261e6026ac0e303c40c6ece9961182 to your computer and use it in GitHub Desktop.
Bash script to install Django Q with Supervisor on Amazon Linux 2 (AL2 / Elastic Beanstalk) - UPD with Procfile in the comments
#!/bin/bash
#Create a copy of the environment variable file.
sudo cp /opt/elasticbeanstalk/deployment/env /opt/elasticbeanstalk/deployment/custom_env_var
#Set permissions to the custom_env_var file so this file can be accessed by any user on the instance. You can restrict permissions as per your requirements.
sudo chmod 644 /opt/elasticbeanstalk/deployment/custom_env_var
#Remove duplicate files upon deployment.
sudo rm -f /opt/elasticbeanstalk/deployment/*.bak
#Install/enable extra packages
sudo amazon-linux-extras install epel -y
#Install supervisor
sudo yum install supervisor -y >/dev/null 2>&1
if [ $? -ne 1 ]; then # Exit on any any error except 'nothing to do'
exit 0
fi
#!/bin/bash
source /var/app/venv/*/bin/activate && {
# collect static
python manage.py collectstatic --noinput;
# log which migrations have already been applied
python manage.py showmigrations;
# migrate user model prior to other models if you have Custom User Model < NB
#python manage.py migrate users --noinput;
# migrate the rest
python manage.py migrate --noinput;
# create superuser
python manage.py createsu;
}
#!/usr/bin/env bash
# Author: Valentine Solonechnyi <valentinesolo@gmail.com>
# Based on instructions by Rémi Héneault
# https://gist.github.com/codeSamuraii/0e11ce6d585b3290b15a9ad163b9aa06
# Django Q supervisor configuration and setup for Amazon Linux 2
mkdir -p /var/log/djangoq/ /var/run/djangoq/
# Get django environment variables
# grep '^PYTHONPATH\|^PATH' no filtering of env variables
# djangoqenv=`cat /opt/elasticbeanstalk/deployment/custom_env_var | tr '\n' ',' | sed 's/=/="/'g | sed 's/,/",/g'`
# fix from @matiszz
djangoqenv=`cat /opt/elasticbeanstalk/deployment/custom_env_var | tr '\n' ',' | sed 's/=/="/'g | sed 's/,/",/g' | sed 's/="="/=="/'g | sed 's/""/"/'g`
djangoqenv=${djangoqenv%?}
# Create djangoq configuraiton script
djangoqconf="[program:django-q]
command=bash -c 'source /var/app/venv/*/bin/activate && python manage.py qcluster'
directory=/var/app/current
user=nobody
numprocs=1
stdout_logfile=/var/log/djangoq/worker.log
stderr_logfile=/var/log/djangoq/worker.log
autostart=true
autorestart=true
startsecs=10
; Need to wait for currently executing tasks to finish at shutdown.
stopwaitsecs = 600
; When resorting to send SIGKILL to the program to terminate it
; send SIGKILL to its whole process group instead,
; taking care of its children as well.
killasgroup=true
stopasgroup=true
environment=$djangoqenv
"
# Create the djangoq supervisord conf script
echo "$djangoqconf" | sudo tee /etc/supervisord.d/djangoq.conf
# Add configuration script to supervisord conf (if not there already)
if ! grep -Fxq "files = supervisord.d/*.conf" /etc/supervisord.conf
then
sed -i "s/*.ini/*.conf/g" /etc/supervisord.conf
fi
#Launch supervisord process
sudo supervisord -c /etc/supervisord.conf
# Reread the supervisord config
sudo supervisorctl reread
# Update supervisord in cache without restarting all services
sudo supervisorctl update
# Start/Restart djangoqd through supervisord.
sudo supervisorctl -c /etc/supervisord.conf restart django-q
#!/usr/bin/env bash
#Supervisor init config
# Add a new configuration of restart Supervisor
sudo cp /var/app/current/supervisord.sample /etc/init.d/supervisord
#Add execute authority
sudo chmod +x /etc/init.d/supervisord
#Add the configuration into system
sudo chkconfig --add supervisord
#Switch on the configuration and start
sudo chkconfig supervisord on
sudo service supervisord start
#! /bin/sh
### BEGIN INIT INFO
# Provides: supervisord
# Required-Start: $remote_fs
# Required-Stop: $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Example initscript
# Description: This file should be used to construct scripts to be
# placed in /etc/init.d.
### END INIT INFO
# Author: Dan MacKinlay <danielm@phm.gov.au>
# Based on instructions by Bertrand Mathieu
# http://zebert.blogspot.com/2009/05/installing-django-solr-varnish-and.html
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Description of the service"
NAME=supervisord
DAEMON=/usr/local/bin/supervisord
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
|| return 1
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
$DAEMON_ARGS \
|| return 2
# Add code here, if necessary, that waits for the process to be ready
# to handle requests from services started subsequently which depend
# on this one. As a last resort, sleep for some time.
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Wait for children to finish too if this is a daemon that forks
# and if the daemon is only ever run from this initscript.
# If the above conditions are not satisfied then add some other code
# that waits for the process to drop all resources that could be
# needed by services started subsequently. A last resort is to
# sleep for some time.
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons dont delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
#
# If the daemon can reload its configuration without
# restarting (for example, when it is sent a SIGHUP),
# then implement that here.
#
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
return 0
}
case "$1" in
start)
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
#reload|force-reload)
#
# If do_reload() is not implemented then leave this commented out
# and leave force-reload as an alias for restart.
#
#log_daemon_msg "Reloading $DESC" "$NAME"
#do_reload
#log_end_msg $?
#;;
restart|force-reload)
#
# If the "reload" option is implemented then remove the
# "force-reload" alias
#
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
exit 3
;;
esac
:
@EdmundoDelGusto
Copy link

Hi!
Works fine for me.

Is it possible, that platform hooks won't be executed, if elastic beanstalk scales?
If I run 'eb scale 5', 4 additional ec2 instances are running but I can see from qmonitor, that only one ec2 instance runs the qcluster.

If I scale back to one ec2 instance, the instance that is actually running may get shut down.

Someone knows any solution to that?

@vlntsolo
Copy link
Author

vlntsolo commented May 6, 2022

Hi @EdmundoDelGusto, that's a good question. I didn't encounter it but have a few suggestions.
Firstly, there's a way to ensure command execution restricted to "leader_only" instance of the autoscaling group.

For .sh files

if [[ "$EB_IS_COMMAND_LEADER" == "true" ]]; then
  COMMANDS
fi

Or as an alternative commands which are triggered from .ebextensions folder within somename.config file:

container_commands:
  name of container_command:
    command: "command to run"
    leader_only: true

Secondly, there's also a shortcut to skip all supervisor configuration and create a Procfile in the app root. I'm using it now on Heroku for running django-q worker process. I have no idea, how it will behave in Beanstalk, but it definitely worth a try.

web: gunicorn <coreapp>.wsgi
worker: python manage.py qcluster --settings=<coreapp>.settings

, where <coreapp> - is your django app name with settings module.

Configuring the WSGI server with a Procfile
Extending Elastic Beanstalk Linux platforms

I don't have a sandbox beanstalk to test autoscaling, so I'd appreciate if you share your results.

@EdmundoDelGusto
Copy link

Thank you for all the help.

I think I messed up when I additionally added sudo yum install supervisor to the container_commands. I am not sure. But it looked like this was causing the other instances to fail. (I'm fairly inexperienced with elastic beanstalk). But now, if I scale up, django q runs on all instances.

I got some additional problems that someone may also run into:

  1. When creating the scripts with Windows, you need to remove the \r symbols Windows adds.

I've done this in my .ebextensions/django.config

  009_remove_windows_symbols:
    command: "sed -i 's/\r$//' .platform/hooks/postdeploy/01_set_env.sh"
  010_remove_windows_symbols:
    command: "sed -i 's/\r$//' .platform/hooks/postdeploy/02_django_q.sh"
  011_remove_windows_symbols:
    command: "sed -i 's/\r$//' .platform/hooks/postdeploy/03_supervisor_init.sh"
  012_remove_windows_symbols:
    command: "sed -i 's/\r$//' supervisord.sample"

(This probably could be done more efficiently)

  1. I needed to make all scripts executable in github.
git add --chmod=+x -- .platform/*/*/*.sh 
git add --chmod=+x -- *.sample 

(I am not sure if all operations are necessary)

Open Problem

One open problem is, that I think the last script (04) does not do anything for me.
If I log into the instance with eb ssh and run sudo service supervisord stop nothing happens. Same for all other commands. No output whatsoever and django_q keeps running.

@vlntsolo
Copy link
Author

vlntsolo commented Oct 30, 2022

Beanstalk supports processes configuration through Procfile file in your project root.

I strongly recommend to replace this gist configuration with Procfile worker description.
Sample with gunicorn server:

web: gunicorn --bind :8000 --workers 3 --threads 2 yourapp.wsgi:application
worker: python manage.py qcluster --settings=yourapp.settings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment