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
dokku ps #298
dokku ps #298
Changes from 23 commits
4d466d4
9917040
ca692a8
d6403d9
c9ad455
c2fb899
16ff437
963d3ed
9572f29
a14d521
24e5726
0f34115
9e51a6a
9ac943f
bd76607
82f5944
1b0999b
b53e109
7eeb458
072ba89
3c2b0a2
8e4797a
d45d8dc
3ac2f67
01648dc
bf7d6ad
1235bcd
3fa3503
ebc3708
a1aa2c1
7f62fce
21f7cd1
b79e5ad
d7e2e09
bf3282e
6490e56
37e8634
c714454
bf49ec0
d8e88fc
f22ef2a
b140216
f01ba4e
e24b447
97d6fe6
d7483cb
8c504dc
73446e8
8f60caf
8db8674
db25b85
295ff91
54e3515
5810c78
e9c0cef
e68dd7b
12d0aee
49e1d8c
24d2874
5a61c5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
dokku-ps | ||
======== | ||
|
||
A process management plugin for [dokku](https://github.com/progrium/dokku). | ||
|
||
Modeled after the process/dyno management of heroku. | ||
|
||
Written to solve these problems: | ||
|
||
1. Restarting crashed containers. | ||
|
||
Because sometimes the process exits because of something like a memory leak and, until we have it fixed, we just want it to keep going. | ||
|
||
And the idea is to use upstart to handle the containers, as described in [dockers host integration docs](http://docs.docker.io/en/latest/use/host_integration/). | ||
|
||
2. Being able to start up non `web` processes. | ||
|
||
Bonus problems to solve: | ||
|
||
1. Monitor & notify. | ||
|
||
I'm guessing since upstart is monitoring the respawning of containers it should be able to notify me by email, irc or something even cooler. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. Would be a great separate plugin |
||
|
||
2. Process scaling. | ||
|
||
If we are able to add each process as a backend to the nginx config we should easily be able to load balance the requests over multiple processes. [This PR](https://github.com/progrium/dokku/pull/267) seems like it would help a lot as we would pretty much just generate `$APP_ROOT/nginx.conf` with the processes defined in `$APP_ROOT/PS`. Something like: | ||
|
||
CONF="$APP_ROOT/nginx.conf" | ||
echo "upstream $APP {" > $CONF | ||
grep "^web=" $APP_ROOT/PS | while read line; do | ||
NUM=${line#*=} | ||
for ((i=1; i<=$NUM; i++)); do | ||
PORT=$(docker port $APP.web.$i 5000 | sed 's/0.0.0.0://') | ||
echo "server 127.0.0.1:$PORT;" >> $CONF | ||
done | ||
done | ||
echo "}" >> $CONF |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
#!/usr/bin/env bash | ||
set -eo pipefail | ||
|
||
# | ||
# dokku ps | ||
# | ||
# ex. | ||
# | ||
# $ dokku ps | ||
# | ||
# === web: `bundle exec thin start -p $PORT` | ||
# web.1: up for 5m | ||
# | ||
# === worker: `bundle exec rake jobs` | ||
# worker.1: running for 1h | ||
# | ||
# | ||
# dokku ps:scale PROC1=AMOUNT1 [PROC2=AMOUNT2 ...] | ||
# dokku ps:restart PROC | ||
# dokku ps:stop PROCS | ||
# | ||
|
||
function ps_list(){ | ||
APP="$1"; | ||
APP_ROOT="$DOKKU_ROOT/$APP" | ||
APP_PS="$APP_ROOT/PS" | ||
echo | ||
ps_proc_list $APP | while read i; do | ||
NAME=$(cut -d":" -f1 <<< "$i") | ||
CMD=$(cut -d":" -f2 <<< "$i") | ||
echo "=== $NAME: \`${CMD/ /}\`" | ||
grep "^$NAME=" $APP_PS | while read line; do | ||
NUM=${line#*=} | ||
for ((i=1; i<=$NUM; i++)); do | ||
ps_status $APP $NAME $i | ||
done | ||
done | ||
done | ||
echo | ||
} | ||
|
||
function ps_scale(){ | ||
APP="$1"; shift; PAIRS="$@"; | ||
APP_ROOT="$DOKKU_ROOT/$APP" | ||
APP_PS="$APP_ROOT/PS" | ||
|
||
# convert the arguments into an array | ||
pairs=( $PAIRS ) | ||
|
||
# store the current list of processes | ||
PS=$(cat $APP_PS) | ||
|
||
# reset PS | ||
# TODO use temp file instead and then `mv` should be safer | ||
echo -n > $APP_PS | ||
|
||
# get the list of available procs (according to Procfile) | ||
ps_proc_list $APP | while read i; do | ||
NAME=$(cut -d":" -f1 <<< "$i") | ||
|
||
# a variable to check if it was found | ||
found="" | ||
|
||
# see if $NAME is found among the $PAIRS | ||
for line in ${PAIRS[@]}; do | ||
name=${line%%=*} | ||
to=${line#*=} | ||
|
||
# found it | ||
if [ "$name" = "$NAME" ]; then | ||
# verify that $to is a number | ||
if [ $to -ge 0 2>/dev/null ]; then | ||
found=1 | ||
echo "$name=$to" >> $APP_PS | ||
else | ||
echo " Skipping \"$name\". \"$to\" is not an integer." | ||
fi | ||
fi | ||
done | ||
|
||
# in case it wasn't an argument use the current one | ||
if [ -z "$found" ]; then | ||
grep "^$NAME=" <<< $PS >> $APP_PS | ||
fi | ||
done | ||
|
||
# output the final PS | ||
cat $APP_PS | ||
|
||
# now deploy! | ||
ps_deploy $APP | ||
} | ||
|
||
function ps_deploy(){ | ||
APP="$1"; IMAGE="app/$APP" | ||
APP_ROOT="$DOKKU_ROOT/$APP" | ||
APP_PS="$APP_ROOT/PS" | ||
|
||
cat $APP_PS | while read line; do | ||
NAME=${line%%=*} | ||
NUM=${line#*=} | ||
|
||
# stop any running processes first otherwise | ||
# `docker kill` will just make it restart | ||
ps_each stop $APP $NAME $NUM || true | ||
|
||
for ((i=1; i<=$NUM; i++)); do | ||
# remove container if already exists | ||
# outputs the id if existed, nothing otherwise | ||
docker kill "$APP.$NAME.$i" > /dev/null | ||
docker rm "$APP.$NAME.$i" > /dev/null | ||
|
||
# create a named container using `docker run` | ||
id=$(docker run -d -name "$APP.$NAME.$i" -p 5000 -e PORT=5000 $IMAGE /start $NAME) | ||
|
||
# TODO make sure it started? | ||
|
||
# stop the container again (to be started using upstart instead) | ||
docker stop $id > /dev/null # outputs the id if existed, nothing otherwise | ||
done | ||
echo "-----> Starting $NAME" | ||
ps_each start $APP $NAME $NUM | ||
echo " $NUM processes up and running" | ||
sleep 5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it necessary to add a mandatory 5 seconds to all deploys? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Woops, that was left there from when I thought docker needed some time to give me a port. |
||
done | ||
} | ||
|
||
function ps_stop(){ | ||
APP="$1"; NAME="$2"; | ||
APP_ROOT="$DOKKU_ROOT/$APP" | ||
APP_PS="$APP_ROOT/PS" | ||
|
||
# require name | ||
if [ -z "$NAME" ]; then | ||
echo "Missing PROC for \"$APP\"" | ||
exit 1 | ||
fi | ||
|
||
grep "^$NAME=" $APP_PS | while read line; do | ||
ps_each stop $APP ${line%%=*} ${line#*=} | ||
done | ||
} | ||
|
||
function ps_status(){ | ||
APP="$1"; NAME="$2"; SEQ="$3"; | ||
# formatting is based on | ||
# https://github.com/dotcloud/docker/blob/b038b0cd44e152fa4158f2bfe145de6d774dd8d0/state.go#L20 | ||
STATUS=$(docker ps -a | grep "$APP.$NAME.$SEQ" | grep -Eo '(Up [0-9]+ [a-z]+|Ghost|Exit [0-9]+)') | ||
echo "$NAME.$SEQ: $STATUS" | ||
} | ||
|
||
function ps_restart(){ | ||
APP="$1"; NAME="$2"; | ||
APP_ROOT="$DOKKU_ROOT/$APP" | ||
APP_PS="$APP_ROOT/PS" | ||
|
||
# default to all procs | ||
if [ -z "$NAME" ]; then | ||
NAME=$(ps_proc_list $APP | cut -d":" -f1) || { | ||
echo "App \"$APP\" has no processes to restart" >&2 | ||
exit 1 | ||
} | ||
fi | ||
|
||
grep "^$NAME=" $APP_PS | while read line; do | ||
ps_each restart $APP ${line%%=*} ${line#*=} | ||
done | ||
} | ||
|
||
function ps_each(){ | ||
CMD="$1"; APP="$2"; NAME="$3"; NUM="$4" | ||
for ((i=1; i<=$NUM; i++)); do | ||
sudo $CMD dokku-ps APP=$APP NAME=$NAME SEQ=$i || { | ||
# echo " -- failed with exit $? --" | ||
true # don't skip the rest if one was already stopped/started | ||
} | ||
sleep 1 | ||
done | ||
} | ||
|
||
function ps_proc_list(){ | ||
APP="$1"; IMAGE="app/$APP" | ||
# TODO this should probably be something defined in /app/.release | ||
# so it doesn't have to fire up a container every time? | ||
docker run -rm $IMAGE cat /app/Procfile | ||
} | ||
|
||
function ps_usage(){ | ||
cat<<EOF | ||
ps <app> List processes for an app | ||
ps:restart <app> [PROC] Restart an app process | ||
ps:scale <app> PROC1=NUM1 [PROC2=NUM2...] Scale processes by the given amount | ||
ps:stop <app> PROC Stop an app process | ||
|
||
EOF | ||
} | ||
|
||
# Check if name is specified | ||
if [[ $1 == ps ]] || [[ $1 == ps:* ]]; then | ||
if [[ -z $2 ]]; then | ||
echo "You must specify an app name" | ||
exit 1 | ||
else | ||
APP="$2" | ||
PS_FILE="$DOKKU_ROOT/$APP/PS" | ||
|
||
# Check if app exists with the same name | ||
if [ ! -d "$DOKKU_ROOT/$APP" ]; then | ||
echo "App $APP does not exist" | ||
exit 1 | ||
fi | ||
|
||
# Check if app has a PS file | ||
if [ ! -f "$PS_FILE" ]; then | ||
echo "web=1" > $PS_FILE | ||
fi | ||
fi | ||
fi | ||
|
||
case "$1" in | ||
ps) ps_list $APP; exit ;; | ||
ps:scale) shift 2; ps_scale $APP "$@"; exit ;; | ||
ps:restart) shift 2; ps_restart $APP $1; exit ;; | ||
ps:stop) shift 2; ps_stop $APP $1; exit ;; | ||
ps:deploy) ps_deploy $APP; exit ;; # internal | ||
help) ps_usage ;; | ||
esac | ||
cat |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/usr/bin/env bash | ||
set -eo pipefail | ||
|
||
sed -i 's/docker -d -r=true$/docker -d -r=false/' /etc/init/docker.conf | ||
|
||
cat<<EOF > /etc/sudoers.d/dokku-ps | ||
%dokku ALL=(ALL)NOPASSWD:/sbin/start dokku-ps * | ||
%dokku ALL=(ALL)NOPASSWD:/sbin/restart dokku-ps * | ||
%dokku ALL=(ALL)NOPASSWD:/sbin/stop dokku-ps * | ||
EOF | ||
|
||
cat<<EOF > /etc/init/dokku-ps.conf | ||
instance \$APP.\$NAME.\$SEQ | ||
description "Dokku PS \$APP.\$NAME.\$SEQ" | ||
author "Dokku PS" | ||
start on filesystem and started lxc-net and started docker | ||
stop on runlevel [!2345] | ||
respawn | ||
respawn limit 10 5 | ||
kill signal KILL | ||
exec /usr/bin/docker start -a "\$APP.\$NAME.\$SEQ" | ||
post-stop exec /usr/bin/docker kill "\$APP.\$NAME.\$SEQ" | ||
EOF |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ git commit -m 'initial commit' | |
REPO="test-$(basename $APP)-$RANDOM" | ||
git remote add target dokku@$TARGET:$REPO | ||
git push target master | ||
URL=$(ssh dokku@$TARGET url $REPO)$FORWARDED_PORT | ||
URL=$(echo | ssh dokku@$TARGET url $REPO)$FORWARDED_PORT | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. echo? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah the ssh connections never ends otherwise so it just stalls. And |
||
sleep 2 | ||
./check_deploy $URL && echo "-----> Deploy success!" || { | ||
sleep 4 | ||
|
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 figure out a way to make it so
dokku
doesn't depend on a plugin.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 it's a bit of a specific hack but would it suffice to wrap the current code in an if?
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 really don't think I'd +1 this without a real solution to this.
On Thu, Nov 7, 2013 at 12:37 PM, Robert Sköld notifications@github.comwrote:
Jeff Lindsay
http://progrium.com
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.
What do you think of this other PR: #296, maybe it could provide a bit more flexible solution?
But, if I understood the current implementation of pluginhook correctly, it would still do multiple deploys which seems a bit wasteful but maybe a flag to pluginhook which makes it only execute the last hook? Or is there a case when multiple deploy scripts would be desirable?