Why this blog?

Thanks for docker, it can’t be changed for the published ports once a container has been created. But sometimes we may need another port for temporarily testing, and don’t want to re-create the container again(because we wouldn’t stand losing anything in our stateful container). So, I think there maybe many guys like me, want to binding other ports for the running state container.

Well, “talk is cheap”, I will show you the code as much as possible. (actually, I am week at English, lol…)

How to?

First, we take a python3 image as the experimental material

(Why python3, because it’s easy to start a web server on python)

docker pull python:3.6-alpine

It’s very small, just 87MB

Start a web server with the testing port 8080

docker run -it -p 8080:8080 --rm python:3.6-alpine sh

export the 8080 port for testing the web server

# run in container
mkdir -pv /tmp/www && cd /tmp/www
echo "hello world!" > index.html
python3 -m http.server 8080

This far, you have started a web server and you can access it directly by http://$host_ip:8080 in you host machine or any other LAN machine

8080

try another port not published on creation

stop the web server with Ctrl-C

# try another port
python3 -m http.server 8090

Now, other LAN machine can’t access the web server, and you can’t access by http://$host_ip:8090 any more. How the container expose the 8080 port to host? Let’s check the iptables list:

sudo iptables -t nat -L -n  # list all rule
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           
MASQUERADE  all  --  172.19.0.0/16        0.0.0.0/0           
MASQUERADE  all  --  172.18.0.0/16        0.0.0.0/0           
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:8080

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:8080

Just focus on PREROUTING and DOCKER chain

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain DOCKER (2 references)
target     prot opt source               destination         
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:8080

The dst address of the 8080 dst port packet will be modified to 172.17.0.2:8080 before routing, it means all the packets accessing $host:8080 will be redirected to 172.17.0.2:8080(the web server container).

So, we also add a similar rule for 8090 port

sudo iptables -t nat -A DOCKER -p tcp --destination-port 8090 -j DNAT --to-destination 172.17.0.2:8090

List the DOCKER chain and check

Chain DOCKER (2 references)
target     prot opt source               destination         
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:8080
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:8090

We have successfully added a rule for 8090 port

Access http://$host_ip:8090 on browser

8090

Good, it works, and it also can be accessed on the other LAN machine. Is it easy? You also can try it. 😉

With a bit more advanced

You can put the follow script into a file, grant it executable permissions, and put into a $PATH dir

#!/bin/bash -e
if [ $# != 3 ]; then
    echo "Usage: $0 CONTAINER_NAME CONTAINER_IP HOST_IP"
    exit 1
fi
CONTAINER=$1
CONTAINER_PORT=$2
HOST_PORT=$3
CONTAINER_IP=$(docker inspect $CONTAINER | jq .[0].NetworkSettings.IPAddress | sed -r 's/"(.+)"/\1/')
sudo iptables -t nat -A DOCKER -p tcp --dport $HOST_PORT -j DNAT --to-destination ${CONTAINER_IP}:$CONTAINER_PORT

It my first time to try English blog. If it need to title this blog in Chinese, I think, “利用iptables给运行中的docker容器绑定端口” may be appropriate.)

(End)

发表评论

电子邮件地址不会被公开。 必填项已用*标注