きくらげ観察日記

好きなことを、適当に。

docker + flask の環境でホスト上にポートが公開できない

問題となったコード

app.py

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'hello world'

if __name__ == '__main__':
    app.run()

Dockerfile

FROM ubuntu:xenial

WORKDIR /flask

ENV PYENV_ROOT /pyenv
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH

RUN apt -y update 2>/dev/null
RUN BUILD_DEPS=" \
    git make build-essential libssl-dev zlib1g-dev libbz2-dev \
    libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
    xz-utils tk-dev" \
    && apt -y install $BUILD_DEPS \
    && git clone https://github.com/yyuu/pyenv.git /pyenv \
    && pyenv install 3.5.3 \
    && pyenv local 3.5.3 \
    && pyenv rehash

COPY . /flask

RUN pip install -r requirements.txt

RUN apt -y autoremove

症状

$ sudo docker build -t cloudear8/flask .
$ sudo docker run -p 5000:5000 --name flask_1 cloudear8/flask python app.py

すると5000番ポートからflaskに繋がるはずが、何故か接続できません。
flask自体のログを見ても、リクエストすら届いていない模様。
というか、コンテナの5000番ポートに届かない……

$ sudo docker inspect flask_1 | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",
$ telnet 172.17.0.2 5000
Trying 172.17.0.2...
telnet: Unable to connect to remote host: Connection refused

やったこと

試しにnetcatで適当なポート番号をlistenしてみた所、そちらには繋がりました。

$ sudo docker exec -it flask_1 /bin/bash
root@dd93148cb513:/flask# apt install netcat
root@dd93148cb513:/flask# nc -l -p 1234 -e /bin/cat

(別ホスト上)

$ telnet 172.17.0.2 1234
Trying 172.17.0.2...
Connected to 172.17.0.2.
Escape character is '^]'.
hello
hello
world
world
^]
telnet> Connection closed.

また、コンテナ内からはflaskに繋がるようです。

root@dd93148cb513:/flask# nc localhost 5000
GET / HTTP/1.1

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 11
Server: Werkzeug/0.12.1 Python/3.5.3
Date: Sat, 06 May 2017 15:19:40 GMT

hello world

というわけで、netcatをプロキシ代わりにして5000番に回すようにしてみたところ…

root@dd93148cb513:/flask# mkfifo pipe
root@dd93148cb513:/flask# nc -l -p 1234 <pipe | nc localhost 5000 >pipe 

(別ホスト上)

$ telnet 172.17.0.2 1234
Trying 172.17.0.2...
Connected to 172.17.0.2.
Escape character is '^]'.
GET / HTTP/1.1

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 11
Server: Werkzeug/0.12.1 Python/3.5.3
Date: Sat, 06 May 2017 15:23:18 GMT

hello worldConnection closed by foreign host.

つながりました。ということはflask自体の問題?

解決策

ここまで色々やってから気付いたのでアレなのですが、dockerとか関係なくflask自体の問題でした。

flaskの公式サイトによると

Externally Visible Server

If you run the server you will notice that the server is only accessible from your own computer, not from any other in the network. This is the default because in debugging mode a user of the application can execute arbitrary Python code on your computer.

If you have the debugger disabled or trust the users on your network, you can make the server publicly available simply by adding --host=0.0.0.0 to the command line:

flask run --host=0.0.0.0

This tells your operating system to listen on all public IPs.

というわけで、app.pyの最後の行を

    app.run(host='0.0.0.0')

に書き換えると、外からでも見えるようになりました。

おわり