Simplifying port forwarding with LXD
LXD containers are brilliant, but lacks an easy way to forward ports from the containers to the host. One can use iptables manually, of course, but I really missed something easy like Docker. To try and remedy this, I have conjured up a little bash script. With this script, you can add, delete and list port forwarding rules.
It's a bit rough around the edges, but maybe I'll tidy it up a bit some day.
Read on for the script and examples.
Adding a rule
Let's say you have an SMTP server running in a container named 'mailbox'. To forward port 25 on the container to port 25 on the host, just do this:
$ lxd-forward add mailbox 25
Another example, what if you have a web server running in the container 'tomcat', on port 8080, and want it forwarded to port 80 on the host?
$ lxd-forward add tomcat 8080 80
Listing rules
To list rules, simply type:
$ lxd-forward list
Sample output:
Rule # LXD IP Host Container
2 kolab 172.29.46.196 143 143
Deleting a rule
To delete a rule, get the rule number from the 'list' command, e.g. 2, and do:
$ lxd-forward delete 2
The script
Here's the whole thing. Keep in mind that some things are hard coded, like the ethernet device, so some modification may be required for it to run in another environment.
#!/usr/bin/env bash
cmd=$1
container_name=$2
container_port=$3
host_port=$4
function usage() {
echo "Usage: $(basename ${0}) [add|list|delete|help] [container] [port] [host port]"
exit 1
}
function error() {
echo "$1"
exit 1
}
test -n "$cmd" || usage
function list() {
printf "Rule #\tCont\tIP\t\tHost port\tContainer port\n"
containers=$(lxc list | grep RUNNING | awk '{print $2, $6}')
echo "${containers}" | while read line; do
c_name=$(echo $line | awk '{print $1}')
c_ip=$(echo $line | awk '{print $2}')
forwarded_ports=$(iptables -t nat --line-numbers -L --numeric | grep $c_ip)
echo $forwarded_ports | while read ports_line; do
test -z "${rule_no}" || break
rule_no=$(echo $ports_line | awk '{print $1}')
host_port=$(echo $ports_line | awk '{print $8}')
host_port=$(echo $host_port | awk -F\: '{print $2}')
container=$(echo $ports_line | awk '{print $9}')
container=$(echo $container | awk -F\: '{print $3}')
if [ "${rule_no}" != "" ]; then
printf "${rule_no}\t${c_name}\t${c_ip}\t${host_port}\t${container}\n"
fi
done
done
}
function forward() {
c_name=$1
c_port=$2
h_port=$3
container_info=$(lxc list $c_name | grep RUNNING | awk '{print $2, $6}' | grep $c_name)
ip=$(echo -n $container_info | awk '{printf("%s",$2)}')
test -n "$ip" || error "Container not found or not assigned an IP address"
test -n "$c_port" || error "Container port not specified"
test -n "$h_port" || h_port=$c_port
iptables -t nat -A PREROUTING -p tcp -i eth0 --dport "$h_port" -j DNAT --to-destination "${ip}:${c_port}"
}
function delete() {
rule_no=$1
test -n "$rule_no" || error "Must specify rule no."
iptables -t nat -D PREROUTING "${rule_no}"
}
case "$cmd" in
"list") list
;;
"add") forward $container_name $container_port $host_port
;;
"delete") delete $2 # Rule no.
;;
*) usage
;;
esac