しゃちの備忘録

プログラミングを中心とした技術関連の備忘録です(今のところ)

Python用のnginx-unitをDockerで起動した

webアプリを作ってみたいなーとおもったので。

とりあえずAPサーバを立ち上げるところまでやってみました。

できたもの

  • Python用のnginx-unitをDockerコンテナ上で起動できる
    • curlでリクエストを送ってレスポンスが返ってくる
    • ルーティングみたいなことはまだ

github.com

各種ファイル解説

ディレクトリ構成

f:id:teru0rc4:20210124163700p:plain

Dockerfile

# NGINX Unit image of Python3.7
FROM nginx/unit:1.21.0-python3.7

# create work directory of container
WORKDIR /usr/src/app

COPY src .

RUN apt-get update \
    && apt-get install -y python3-pip \
    && pip3 install --no-cache-dir -r ./requirements.txt \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# port used by the listener in config.json
EXPOSE 8080

hub.docker.com

ここで配信されているdockerイメージを利用します。

Python3.7かつ現在(2021/01/24)時点で最新のイメージを利用したいので、 FROM nginx/unit:1.21.0-python3.7を指定。

WORKDIRの設定は好み。

RUN では、python3を入れています。 requirements.txtは現段階では何も記述していないので何もおきませんが、後々ここに必要な物を追加していく予定なので、とりあえずそれをもとにpip3 installしてくれるような記述をしておきます。

apt-get cleanは、 Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント に従って記述した、 イメージのサイズ縮小のための処理です。

EXPOSE 8080で、コンテナが接続用にリッスンするポートを指定します。 あくまでコンテナ向けの設定で、外部からアクセスするポートではありません。(それはdocker-compose.ymlで記述)

docker-compose.yml

# 利用するdocker-comnposeのバージョン
version: '3'

# アプリケーションで動かすための各要素
services:
  # サービス名
  timer_app:
#    image: selenium/standalone-chrome-debug
    # 起動するコンテナの設定
    build:
      # ディレクトリの指定
      context: .
      # 使用するDockerfileの名前を指定、「Dockerfile」という名前なら省略可能
      dockerfile: Dockerfile
    container_name: timer_app_container
    # ディレクトリのマウントの設定、:前がホストのディレクトリ、:後がコンテナのディレクトリ
    volumes:
      - ./:/usr/src/app
      - ./nginx:/docker-entrypoint.d
    working_dir: /usr/src/app
    # ポート設定、:前がホスト側のポート、:あとがコンテナ側のポート
    ports:
      - "8000:8080"

composeファイルはだいたいつけたコメントのとおりです。 特にちゃんと解説が必要なものは、./nginx:/docker-entrypoint.dです。

nginx-unitでは制御用の設定をconfig.jsonをputしたりで登録する必要があります。 公式のドキュメントでもこれをするためにcurl -X PUTをしてね、というのですが今回は別の方法で制御用の設定を追加します。

それが今回の./nginx:/docker-entrypoint.dという記述で、/docker-entrypoint.dに、./nginx以下のファイルを共有するという処理です。

この記述のポイントしてそもそも、 コンテナの起動時に実行される/usr/local/bin/docker-entrypoint.shの処理があります。

この処理がどんなものかというと、

#!/usr/bin/env bash

set -e

curl_put()
{
    RET=`/usr/bin/curl -s -w '%{http_code}' -X PUT --data-binary @$1 --unix-socket /var/run/control.unit.sock http://localhost/$2`
    RET_BODY=${RET::-3}
    RET_STATUS=$(echo $RET | /usr/bin/tail -c 4)
    if [ "$RET_STATUS" -ne "200" ]; then
        echo "$0: Error: HTTP response status code is '$RET_STATUS'"
        echo "$RET_BODY"
        return 1
    else
        echo "$0: OK: HTTP response status code is '$RET_STATUS'"
        echo "$RET_BODY"
    fi
    return 0
}

if [ "$1" = "unitd" -o "$1" = "unitd-debug" ]; then
    if /usr/bin/find "/var/lib/unit/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then
        echo "$0: /var/lib/unit/ is not empty, skipping initial configuration..."
    else
        if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then

           ... <> ...

            echo "$0: Looking for configuration snippets in /docker-entrypoint.d/..."
            for f in $(/usr/bin/find /docker-entrypoint.d/ -type f -name "*.json"); do
                echo "$0: Applying configuration $f";
                curl_put $f "config"
            done

           ... <> ...

        else
            echo "$0: /docker-entrypoint.d/ is empty, skipping initial configuration..."
        fi
    fi
fi

exec "$@"

要約すると、docker-entrypoint.d*.jsonをに対して、putを実行する処理を含んでいます。

そして、もう一つ。 今回のディレクトリ構成では./nginx以下に、config.jsonを格納しています。

なので、./nginx:/docker-entrypoint.dという記述で、/docker-entrypoint.dに、./nginx以下のファイルを共有すると、/usr/local/bin/docker-entrypoint.shが設定を自動的に追加してくれます。

config.json

{
  "listeners": {
    "*:8080": {
      "pass": "applications/python"
    }
  },
  "applications": {
    "python": {
      "type": "python",
      "path": "/usr/src/app/src/",
      "module": "wsgi"
    }
  }
}

App Samples — NGINX Unit で配布されていたサンプルほぼそのままです。 pathを自分のディレクトリ構成に合わせて修正しています。

wsgi.py

# -*- coding: utf-8 -*-

def application(environ, start_response):
    start_response("200 OK", [("Content-Type", "text/plain")])
    return (b"Hello, Python on Unit!")

App Samples — NGINX Unit で配布されていたサンプルそのままです。

動かし方

下で起動できます

 docker-compose build --no-cache
 docker-compose up -d

コンテナ内に入るときは

 docker-compose exec timer_app bash

curl localhost:8000で、レスポンスが返ってくれば正常に動いています。

f:id:teru0rc4:20210124165922p:plain

参考

unit.nginx.org

だいたいのソースコードはここ。 ただしconfigの設定あたりで訳わからなくなっていろいろ調べることにしてました。


unit.nginx.org

コンテナで利用する際にconfigが必須ということを理解したセクション。 なんとかしてconfigを追加したいなと考えていたのはこの辺り。


lab.astamuse.co.jp

/var/run/control.unit.sockに対してconfig.jsonを反映させることができれば良いことを理解した記事。 ただcurlをするのは面倒なので何か別の方法がないかなと思い調べてみました。


qiita.com

config.json/docker-entrypoint.d/ に入れることで設定ができることを知った記事。 それならdockerの設定でよしなにできそうだなと思ったのでその方向で進めることに。 ただし、なんでそれでできるんだ……ということがわからなかったのでもう少し調べてます。


www.wantanblog.com

その答えがあった記事。 どうやら、/usr/local/bin/docker-entrypoint.shdocker-entrypoint.dのconfigを配置してくれる処理を含んでいるので、 確信を持って進めることができました。 ありがたい。。。(どうやってこういうのに気がつくんだろう)


qiita.com

ホスト側から制御をするメリットについてかいてあった記事。 そういう観点もあるんだなぁとなりました。