Fabulxc for LXC on remote servers through tunneling
Pierre-Henri Cumenge3 min read
With the intent of testing our projects in separate environments, I have been working recently on simple fabric scripts for automating LXC containers creation; a basic one can be found at https://github.com/raphaelpierquin/fabulxc, however the context was slightly more complicated here, since we intended to create those on our remote test server and not locally. A more thorough (and specific) installation was also required, with database creation, automated symfony projects installation and server deployment.
In the following tutorial, I adopted a “get things done” mindset. There are many nice-to-haves that will be implemented in the future, such as defining the containers’ ips by DHCP and not statically.
For the most part, the LXC creation is pretty straightforward and requires basically the replacement of the
local
calls by
run
calls in fabulxc.
The root directory of container cont_name is accessible under
/var/lib/lxc/cont_name/rootfs/,
which allows for adding or modifying files. For instance adding your public key for authentication is pretty easy since we can copy it directly from the remote server at
/var/lib/lxc/cont_name/rootfs/root/.ssh/authorized_keys :
that can be done even before starting the lxc container.
The first real issue is to be able to work inside the containers: in order to install packages or launch a service however, we want our script to be able to access the container itself. Working inside the container, itself in a remote server, is going to require tunneling through the server.
Tunneling seems sadly not to be fully supported by fabric (cf open issue https://github.com/fabric/fabric/issues/38, apparently there are some issues related to paramiko), so after having a quick look at the existing options I could find (https://gist.github.com/e3e96664765748151c05 and https://gist.github.com/e3e96664765748151c05), I chose to go the simple way*, i.e. do it myself by hand :
def open_tunnel(ip): print ‘Opening tunnel’ process = subprocess.Popen([‘ssh’, ‘-N’, ‘-L1248:’ + ip + ‘:22’, ‘root@example.theodo.fr’]); sleep(2) return process
def close_tunnel(process): print ‘Closing tunnel’ process.terminate()
#Calls a function inside a tunnel @task @hosts(‘root@127.0.0.1:1248’) def tunnel_wrap(function_name, name): #finds the (static) ip of the container with settings(host_string = ‘root@example.theodo.fr:22’): ip = get_container_ip(name) function = eval(function_name) p = open_tunnel(ip) with settings(warn_only=True): function(name) close_tunnel(p)
Say we want to install our packages and have defined aninstall_packages
function, we only need to call
tunnel_wrap (‘install_packages’, cont_name)
to install the packages for the container “cont_name”.
Basically, we just open a tunnel on port 1248 to the desired container, execute our function, then close the tunnel.
The
warn_only=True
environment setting allows the closing of the tunnel even when the executed function sends back an error (since fabric stops by default at the first failure).
*This is obviously only a workaround, but it serves our purpose quite well. I intend to be testing the solutions cited before soon, though, hopefully there will be a post about that later :).