This repository has been archived by the owner on Sep 26, 2019. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SuperHAProxy is a script around HAProxy to add support for live migration and easy handling of multiple instances. Live migration is added using the CRIU system. Very lightweight containers are used to isolate the proxy instances from each other to enable live migration to efficiently transfer the proxies and to ensure instances can be running different versions to support proper rolling upgrades. This script can be used stand alone, or as a building block to provide multiserver proxy pools that are high performance and support a workflow where the servers can be rolling upgraded live without connection loss. Change-Id: Id3150c5d5de6c3f5552f27f79cbcf0debe8adb1f
- Loading branch information
Showing
1 changed file
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,346 @@ | ||
#!/bin/bash | ||
|
||
#Document the bridge setup.... | ||
#ovs-vsctl set bridge shabr stp_enable=false | ||
|
||
#FIXME not all of them work... hardcoding for now. | ||
#mirror=$(curl -s http://nl.alpinelinux.org/alpine/MIRRORS.txt | shuf | head -n 1) | ||
mirror="http://dl-6.alpinelinux.org/alpine/" | ||
#FIXME write some logic to detect this. | ||
version=2.6.5-r1 | ||
statedir=/var/lib/superhaproxy | ||
wrapperurl='http://git.haproxy.org/?p=haproxy-1.6.git;a=blob_plain;f=src/haproxy-systemd-wrapper.c;hb=HEAD' | ||
#FIXME make this configurable | ||
bridge=shabr | ||
|
||
function init_config { | ||
name="$1" | ||
ip=$(crudini --get "$statedir/containers/$name/container.ini" superhaproxy ip) | ||
subnet=$(crudini --get "$statedir/containers/$name/container.ini" superhaproxy subnet) | ||
gateway=$(crudini --get "$statedir/containers/$name/container.ini" superhaproxy gateway) | ||
mtu=$(crudini --get "$statedir/containers/$name/container.ini" superhaproxy mtu) | ||
} | ||
|
||
function get_pid_file { | ||
echo "$statedir/containers/$1/container.pid" | ||
} | ||
|
||
function get_pid { | ||
echo "$(< "$statedir/containers/$1/container.pid")" | ||
} | ||
|
||
function get_dump_dir { | ||
echo "$statedir/dumps/$1" | ||
} | ||
|
||
function get_container_dir { | ||
echo "$statedir/containers/$1" | ||
} | ||
|
||
if [ "x$1" == "x" ] | ||
then | ||
echo "Usage:" | ||
echo " init" | ||
echo " list" | ||
echo " create" | ||
echo " show" | ||
echo " start" | ||
echo " stop" | ||
echo " reload" | ||
echo " pid" | ||
echo " pstree" | ||
echo " shell" | ||
echo " hatop" | ||
echo " dump local" | ||
echo " restore local" | ||
exit -1 | ||
fi | ||
|
||
if [ "x$1" == "xinit" ] | ||
then | ||
mkdir -p $statedir | ||
if [ ! -d $statedir/alpine-tools ] | ||
then | ||
mkdir -p $statedir/alpine-tools | ||
pushd $statedir/alpine-tools | ||
curl ${mirror}/latest-stable/main/x86_64/apk-tools-static-${version}.apk | tar -zxf - | ||
popd | ||
fi | ||
if [ ! -d $statedir/rootimg ] | ||
then | ||
mkdir -p $statedir/rootimg | ||
$statedir/alpine-tools/sbin/apk.static -X ${mirror}/latest-stable/main -U --allow-untrusted --root $statedir/rootimg --initdb add alpine-base haproxy | ||
#FIXME this makes way too big a binary. Remove once alpine provides the wrapper | ||
curl -s "$wrapperurl" -o $statedir/wrapper.c | ||
gcc --static -o $statedir/rootimg/usr/sbin/haproxy-systemd-wrapper $statedir/wrapper.c | ||
#FIXME criu doesn't support checkpinting the chroot yet. | ||
sed -i '/chroot/d' $statedir/rootimg/etc/haproxy/haproxy.cfg | ||
fi | ||
mkdir -p $statedir/containers | ||
mkdir -p $statedir/dumps | ||
mkdir -p $statedir/action-scripts | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xlist" ] | ||
then | ||
ls $statedir/containers/ | cat | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xcreate" ] | ||
then | ||
shift | ||
ip="" | ||
name="" | ||
subnet="255.255.255.0" | ||
gateway="" | ||
mtu=9000 | ||
while getopts ":i:m:n:s:g:" opt; do | ||
case ${opt} in | ||
i ) | ||
ip="$OPTARG" | ||
;; | ||
m ) | ||
mtu="$OPTARG" | ||
;; | ||
s ) | ||
subnet="$OPTARG" | ||
;; | ||
n ) | ||
name="$OPTARG" | ||
;; | ||
\? ) echo "Usage: superhaproxy create [-m mtu] [-s subnetmask] [-g gatewayip] -i ip_address -n name" | ||
exit -1 | ||
;; | ||
esac | ||
done | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name with -n" | ||
exit -1 | ||
fi | ||
if [ "x$ip" == "x" ] | ||
then | ||
echo "You must specify an ip with -i" | ||
exit -1 | ||
fi | ||
cp -a $statedir/rootimg "$statedir/containers/$name" | ||
touch "$statedir/containers/$name/container.ini" | ||
crudini --set "$statedir/containers/$name/container.ini" superhaproxy ip "$ip" | ||
crudini --set "$statedir/containers/$name/container.ini" superhaproxy mtu "$mtu" | ||
crudini --set "$statedir/containers/$name/container.ini" superhaproxy subnet "$subnet" | ||
crudini --set "$statedir/containers/$name/container.ini" superhaproxy gateway "$gateway" | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xshow" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
init_config "$name" | ||
echo "IP: $ip" | ||
echo "Subnet Mask: $subnet" | ||
if [ "x$gateay" != "x" ] | ||
then | ||
echo "Gateway: $gateway" | ||
fi | ||
echo "MTU: $mtu" | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xstart" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
init_config "$name" | ||
container="$(get_container_dir "$name")" | ||
#FIXME ensure escaping is correct. | ||
unshare --net --mount --pid --fork -- bash -c "/usr/bin/setsid -- /bin/bash -c 'mount --make-rprivate /; mount --bind $container /tmp; cd /tmp; mkdir -p old; pivot_root . old; mount --bind /old/dev /dev; mount /proc /proc -t proc; umount -l old; exec /usr/sbin/haproxy-systemd-wrapper -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid </dev/null >/dev/null 2>&1'" & | ||
sleep 1 | ||
awk '{print $1}' /proc/$!/task/$!/children > "$container/container.pid" | ||
P="$(get_pid "$name")" | ||
ovs-vsctl del-port $bridge "sha$(get_pid "$name")" > /dev/null 2>&1 | ||
ip link add sha$P type veth peer name shai$P | ||
ip link set dev sha$P mtu "$mtu" up | ||
ip link set shai$P netns $P name eth0 | ||
nsenter -t $P -n ip addr add "$ip/$subnet" dev eth0 | ||
nsenter -t $P -n ip link set dev eth0 mtu "$mtu" up | ||
ovs-vsctl add-port $bridge sha$P | ||
exit $? | ||
fi | ||
|
||
if [ "x$1" == "xpid" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
get_pid $name | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xpstree" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
pstree -p $(get_pid "$name") | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xstop" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
kill $(get_pid "$name") | ||
ovs-vsctl del-port $bridge "sha$(get_pid "$name")" | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xshell" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
nsenter -n -m -p -t $(get_pid "$name") /bin/busybox sh | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xhatop" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
hatop -s "$(get_container_dir "$name")/var/lib/haproxy/stats" | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xreload" ] | ||
then | ||
name="$2" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
kill -USR2 $(get_pid "$name") | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xdump" ] | ||
then | ||
subcmd="$2" | ||
if [ "x$subcmd" != "xlocal" ] | ||
then | ||
echo "only local is supported at the moment" | ||
exit -1 | ||
fi | ||
name="$3" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
if [ "x$subcmd" == "xlocal" ] | ||
then | ||
dumpdir=$(get_dump_dir "$name") | ||
rm -rf "$dumpdir" | ||
mkdir -p "$dumpdir" | ||
criu dump -D "$dumpdir" -t "$(get_pid "$name")" --tcp-established --shell-job --ext-mount-map /dev:dev | ||
exit $? | ||
fi | ||
exit 0 | ||
fi | ||
|
||
if [ "x$1" == "xrestore" ] | ||
then | ||
subcmd="$2" | ||
if [ "x$subcmd" != "xlocal" ] | ||
then | ||
echo "only local is supported at the moment" | ||
exit -1 | ||
fi | ||
name="$3" | ||
if [ "x$name" == "x" ] | ||
then | ||
echo "You must specify a name" | ||
exit -1 | ||
fi | ||
if [ "x$subcmd" == "xlocal" ] | ||
then | ||
tmpid=$$ | ||
pidfile=$(get_pid_file "$name") | ||
as="$statedir/action-scripts/$name.sh" | ||
cat > "$as" <<EOF | ||
#!/bin/bash | ||
if [ "x\${CRTOOLS_SCRIPT_ACTION}" == "xpost-restore" ] | ||
then | ||
P=\$(cat "$pidfile") | ||
ip link set dev sha$tmpid name "sha\$P" | ||
ip link set dev "sha\$P" mtu 9000 up | ||
ovs-vsctl add-port $bridge "sha\$P" | ||
fi | ||
EOF | ||
chmod +x "$as" | ||
dumpdir=$(get_dump_dir "$name") | ||
container="$(get_container_dir "$name")" | ||
if [ ! -d "$dumpdir" ] | ||
then | ||
echo "Dump does not exist" | ||
exit -1 | ||
fi | ||
rm -f "$(get_pid_file "$name")" | ||
ovs-vsctl del-port $bridge "sha$(get_pid "$name")" > /dev/null 2>&1 | ||
mount --bind "$container" "$container" | ||
criu restore -d -D "$dumpdir" --shell-job --tcp-established --ext-mount-map dev:/dev --root "$container" --veth-pair eth0="sha$tmpid" --action-script "$as" --pidfile "$(get_pid_file "$name")" | ||
res=$? | ||
umount "$container" | ||
exit $res | ||
fi | ||
exit 0 | ||
fi | ||
|
||
#migrate | ||
#rsync -avz --delete -e ssh /var/lib/superhaproxy/containers/foo 192.168.0.20:/var/lib/superhaproxy/containers/ | ||
# procedure: | ||
# * initial rsync of container | ||
# * dump on local host | ||
# * second rsync of container | ||
# * rsync of images | ||
# * restore on remote host | ||
# * On success | ||
# * rm container and dump on localhost | ||
# * On failure | ||
# * If autofailback | ||
# * Restore container local | ||
# * on restore failure | ||
# * Try starting remote, if works, remove local container/images all done. | ||
# * If failed to start remote, try and start local | ||
# * If state all still local, remove remote data. | ||
|
||
echo "Unknown command: $1" | ||
exit -1 |