9
7

Python3 の Docker コンテナで PermissionError: [Errno 13] Permission denied on Docker

Last updated at Posted at 2019-06-10

Python3 のビルトイン Web サーバの内部ポートを 80 番にすると Docker コンテナが起動しない

Python3 のビルトインサーバのコンテナを 80 番ポート起動すると、「PermissionError: [Errno 13] Permission denied」エラーで起動しない。

「python3 docker PermissionError "Errno 13"」でググっても、ローカルのファイル権限の記事ばかりだったので、自分のググラビリティとして。

TL; DR (今北産業)

  1. python ユーザーなどの非ルート・ユーザーでサーバーを起動していませんか?
  2. 対症療法なら、とりま USER root にする。
    --privileged で起動されない絶対の自信があるなら、安全ではないが root のままでもおk
  3. 結局、ポートを 8080 にして実行ユーザーを python のままにしておくのがらくで安全。

TS; DR コンテナが故に root でも大丈夫な理由とダメな理由のコマケーこと

Python3 の公式コンテナは、ルート権限ユーザーでサーバーを立ち上げないと 80 番ポートが使えません。逆に言えば、Python3 のコンテナは、非ルート権限でサーバーを立ち上げると 80 番ポートが使えなくなります

しかし、非ルート・ユーザーで 80 番ポートが使えないのは Docker の制限ではなく Linux/UNIX 系 OS のセキュリティ上の仕様制限です。

特権ポート番号(Privileged Ports1)である 80 番ポートは root 権限ユーザーでないと開けられません(特権ポートについては後述)。

Python3 の公式コンテナは、デフォルトでユーザーが pythonUSER python) に設定されています。そのため、一番簡単な方法は、USER を明示的に root に指定することです。Dockerfile の下部に USER root ディレクティブを指定すると 80 番ポートが利用可能になります。

セキュリティの観点から非 root ユーザでコンテナを起動する必要がある場合は、ipchainsiptables などで 80 番ポートを 8080 番ポートなどに転送する必要があります。その場合の設定は、コンテナのベースとなる OS に依存します。

特権ポート番号とはPrivileged Ports1(特権ポート)と呼ばれる 0〜1023 の範囲のポート番号のことです。

ウェルノウン・ポート2とも呼ばれます。この範囲のポートを使う場合、実行ユーザーが root でないと Linux/Unix 系 OS の仕様でポートを開けられない3ため「PermissionError: [Errno 13] Permission denied」の権限エラーが出ます。逆に言えば、8000 番や 8080 番ポートなら問題ないと言うことです。

実は、Python3 のビルトイン Web サーバーで Docker のコンテナを作ろうと思い、以下の Qiita 記事を読んで写経をしたところ、ストレートに動作しました。

このコンテナに限らず、Python3 の Web サーバーのデフォルトポートは 8000 番です。(Python v3.7.4rc2 現在)

そのため、今回のこの Python3 コンテナ側の 8000 ポートに http://<コンテナ名>:8000/ で他のコンテナから http アクセスできます。

次にホスト側、つまり Docker ネットワークの外からコンテナにアクセスしたい場合、コンテナを起動時に -p もしくは --publish オプションでポートを公開します。(--ports ではないので注意 → 私)

具体的には docker run -p 8080:8000 -d <YOUR DOCKER IMAGE> でコンテナを起動、もしくは docker-compose.yml の場合は ports: "8080:8000"4 を記載して起動すると、マシンの 8080 ポートをコンテナの 8000 ポートへポートフォワードしてくれます。つまり、コンテナのポート 8000 番とホストマシンのポート 8080 番をつなげてくれます。

これにより他のパソコンからも http://<ホスト OS の IP>:8080/ でコンテナにアクセスできます。(もちろん cURLでも)

Docker ネットワーク内では 80 番ポートでコンテナ間通信したかった

🐒   先に最終的に取った方法を言うと、80 番ポートでの統一を諦めて 8080 番ポートで統一させることにしました。コンテナごとに(CentOS, Debian, alpine など) OS が異なるため、設定や管理がむしろ煩雑になってしまったからです。

今回目指したかったのは Docker ネットワーク内で完結、つまりホスト含め外部にさらす必要のない構成です。

docker-compose up すると複数コンテナが同じ Docker ネットワーク内で起動します。そして、exec コマンドでメインのコンテナにアクセスすると、コンテナ間で http 通信を行ったのち処理結果だけをコンソール(ターミナル)の標準出力に表示させたいのです。

各々のコンテナは PHP7、Golang、Python3 などで書かれた「目的別のシンプルな機能だけを提供している RESTful なコンテナ」です。メインのコンテナは、各コンテナに http リクエストした結果をとりまとめて結果だけを吐き出すイメージです。

その際、仕様をシンプルにするため各々のコンテナのポートを 80 番に固定することで、http://<コンテナ名>/ だけでお互いのコンテナがアクセスできるようにしたかったのです。(同じ Docker ネットワークにいるコンテナ同士に限る)

しかし、PHP7 のビルトイン Web サーバーのコンテナへは http://<PHPコンテナ名>:80/ で接続できるのに、Python3 のビルトイン Web サーバーの場合、コンテナの内側ポートを 80 番に設定すると「PermissionError: [Errno 13] Permission denied」エラーでコンテナが起動しません。デフォルトの 8000 番ポートや 1024 番以上のポートだと起動します。

✅起動する設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=8000)
✅起動する設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=1024)
❌起動しない設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=80)
❌起動しない設定
import http.server
http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=1023)

起動しなかったコンテナのログをみると「PermissionError: [Errno 13] Permission denied」が発生しています。

起動したコンテナのlogs
$ docker container logs sample_container
Traceback (most recent call last):
  File "/server_start.py", line 3, in <module>
    http.server.test(HandlerClass=http.server.CGIHTTPRequestHandler, port=81)
  File "/usr/local/lib/python3.6/http/server.py", line 1185, in test
    with ServerClass(server_address, HandlerClass) as httpd:
  File "/usr/local/lib/python3.6/socketserver.py", line 453, in __init__
    self.server_bind()
  File "/usr/local/lib/python3.6/http/server.py", line 136, in server_bind
    socketserver.TCPServer.server_bind(self)
  File "/usr/local/lib/python3.6/socketserver.py", line 467, in server_bind
    self.socket.bind(self.server_address)
PermissionError: [Errno 13] Permission denied

Permission?え?権限?」と思い調べてみると、PHP7 のビルトインサーバーは root ユーザーで実行しているのに対し、Python3 のビルトインサーバーは Dockerfile 内で python ユーザーを指定していました。ユーザーを root に変更したところ普通に動きました。

「えー、python って root じゃないと 80 番ポート使えないのー」と思ったのですが、どうやらプログラム言語の仕様制限ではなく OS の仕様制限で、単純に実行ユーザーが異なっていたのが原因でした。

となると、取る方法は2つです。

  1. root ユーザーでも 80 番ポートを使えるように iptablesipchains でコンテナ内でポートフォワードする。
  2. root ユーザーでコンテナを起動させる。

しかし「root ユーザーで起動させるとセキュリティ的に大丈夫なのか」という心配も出てきました。

You don't need to add a new username and change everything to adapt it to that unless there's a clear reason to do so. But as the root user inside the container only has access to that container, there's is nothing that user can risk, unless you mount a privileged directory or something similar.

(「Permission error on port 80」| Issue #62 | uwsgi-nginx-flask-docker @ GitHub より)5

【筆者訳】(一部加筆)
明確な理由がない限り、新しいユーザーを作成し、それらが動作するように無理をして設定を変える必要はありません。root ユーザーがアクセスできるのは、そのコンテナ内だけだからです。しかし docker.sock などのホスト側にアクセスできるようなセンシティブなファイルやディレクトリをマウントしている場合は、その限りではありません。

つまり「--privileged で起動させない絶対の自信があるなら、root でもいい」と言うことです。確かに、「コンテナ内で完結できる」という Docker のメリットを考えると一理あります。むしろ、そのため(ホストと隔離させるサンドボックス的)にコンテナを使っているわけですから。

筆者の場合、今のところ、すべてのコンテナは自家製でローカル利用が大半なので root でも問題ないと思います。

しかし、これはホストのマシンが Linux の場合に限ります。つまり macOS や Windows WSL2 上の仮想 Linux マシン上で動かしている場合です。

ホスト OS が Linux の場合は、やはりコンテナ内の実行ユーザーは root でない方が安全です。と言うのも、Docker 自身は root 権限を必要とするためです。

つまり、コンテナの実行ユーザーも root の場合、ホストから接続されたもの(コンテナにマウントされたソケット含むファイルやディレクトリ)は、 root として触れるということになり、意図しないルート(経緯)から意図しない操作を実行される危険性もあるからです。

そのため、自分を信用していないし、色々考えるのも面倒なのでポカよけの意味も込めて 8080 ポートで統一させ、非ルートユーザーで実行させることにしました。

  1. Privileged Ports」@ W3.org 2

  2. ウェルノウンポート番号(0–1023)」| TCPやUDPにおけるポート番号の一覧 @ Wikipedia

  3. PermissionError: [Errno 13] Permission denied Flask.run()」@ StackOverflow

  4. ports | Docker Compose - docker-compose.yml リファレンス(日本語)」 @ Qiita

  5. Permission error on port 80」| Issue #62 | uwsgi-nginx-flask-docker @ GitHub

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7