Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A Docker image #198

Closed
x-yuri opened this issue Dec 6, 2020 · 8 comments
Closed

A Docker image #198

x-yuri opened this issue Dec 6, 2020 · 8 comments

Comments

@x-yuri
Copy link
Contributor

x-yuri commented Dec 6, 2020

In #153 you suggested to create a Docker image. Supposedly to be able to run bjoern. Here's the starting point:

FROM python:alpine
RUN apk add --no-cache build-base libev-dev git \
    && cd /usr/src \
    && git clone https://github.com/jonashaag/bjoern \
    && cd bjoern \
    && git submodule update --init \
    && python setup.py install
$ docker build -t bjoern .

I could download the latest release:

    && wget https://github.com/jonashaag/bjoern/archive/$VERSION.tar.gz -O bjoern.tar.gz \
    && tar xf bjoern.tar.gz -C /usr/src \
    && cd /usr/src/bjoern-*

But it doesn't come with submodules. Although it contains .gitignore, and .gitmodules, so if I do git init... that would probably still fail.

And what do you expect to happen when one does docker run --rm bjoern? And how do I feed an application to it? With e.g. nginx, by default it shows the welcome page. And to put it to practical use you replace the config, and add a directory with static files.

@jonashaag
Copy link
Owner

jonashaag commented Dec 26, 2020

Cool! The default Docker image doesn't have to cover advanced use cases. I'd say we should expected the application to be mounted to /app. I just put together a simple runner script (maybe we can add it too the main repo as well...) that we can add to the Docker image and use as entry point:

import argparse
import functools
import importlib
import multiprocessing

import bjoern


def load_app(app_module):
    try:
        app_module_name, app_obj_name = app_module.rsplit(":", 1)
    except ValueError:
        app_module_name = app_module
        app_obj_name = "application"
    app_module = importlib.import_module(app_module_name)
    try:
        return getattr(app_module, app_obj_name)
    except AttributeError as err:
        raise ImportError(
            f"cannot import name {app_obj_name!r} from {app_module_name!r} ({app_module.__file__})"
        ) from err


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--bind", "-b", type=str, default="127.0.0.1:8000")
    parser.add_argument("--workers", "-w", type=int, default=1)
    parser.add_argument("app_module")
    args = parser.parse_args()

    app_obj = load_app(args.app_module)

    host, port = args.bind.rsplit(":")
    port = int(port)

    workers = [
        multiprocessing.Process(
            target=functools.partial(bjoern.run, app_obj, host, port, reuse_port=True),
            daemon=True,
        )
        for _ in range(args.workers)
    ]
    for w in workers:
        w.start()
    for w in workers:
        w.join()


if __name__ == "__main__":
    main()

Usage is similar to Gunicorn, eg. python runner.py --workers 4 myapplication:app

@Julien00859
Copy link

Julien00859 commented Dec 31, 2020

Hello, thank you to have redirected me here, I read some other issues (mainly related to SIGTERM/SIGKILL) but didn't read this one.

Beware that using a single --bind option to get the ip/port pair makes it difficult to work with unix domain socket (where there is no port) and ipv6 (where the ip should be enclosed in square brackets). IIRC not that much a problem in a docker environment as ipv4 is first class citizen.

Also there are applications that parse the command line arguments too, there can be a clash between bjoern's options and the application's options. In my branch I use parse_known_args and rewritte sys.argv to remove the args parsed by bjoern. An alternative would be to use a special string to partition the wrapper's options from the application's option. Like python runner.py --worker 4 myapplication:app -- --an-app-option --another, parsing only the options before -- and then rewritting sys.argv.

I have some concern about starting multiple workers and to join them afterward. In the current situation I think that if you start the application with 4 workers but 3 fails (outstanding exception, sys.exit, signal, ...) then you are left with just 1 worker going. At work, we have our own (complicated) wsgi server implementation, it is a pre-fork model and it is about 600 python LoC long because it needs to handle signals, resurrect dead workers, and to do other "process management" stuff.

As Bjoern advertise itself to be lightweight, I don't know if it is worth to implement multi-workers capabilities (but hey, I discovered your work last night), in a docker environment (often behind kubernetes) there is already all the necessary to start an application in multiple containers (achieving multi-processing). In just did a few research and it appears it is not too much pain to start multiple workers using systemd either see https://unix.stackexchange.com/questions/288236/have-systemd-spawn-n-processes

Regards,

@jonashaag
Copy link
Owner

jonashaag commented Jan 2, 2021

For the multi worker implementation, we should not add anything complex. If you want resiliency etc. you usually have other requirements specific to your setup that you need to implement anyways. I'm happy to add more robustness as long as it's simple to understand for people not familiar with bjoern or server deployment.

As for the command line design I've just copied whatever Gunicorn has.

@Tecktron
Copy link

If you're looking for a no thrills slightly configurable image to run in a containerized cluster (like kubernetes) where you don't care about workers and the like because you just want to spin up more containers to handle scale (or just to use in a docker-compose file for local dev), then I have ones I built from the base python images to work with WSGI Django projects (I've also tested it with Flask and Bottle). It's not exactly fancy pants, but has enough config options that I hopefully documented clearly and it works well for me. Hopefully someone will find it of use as well.

https://hub.docker.com/r/tecktron/python-bjoern/
https://github.com/Tecktron/docker-python-bjoern

@pykler
Copy link

pykler commented Dec 6, 2021

@Tecktron any reason for using the large python base image instead of python:alpine mentioned in this ticket?

@Tecktron
Copy link

Tecktron commented Dec 7, 2021

@pykler I've often run into issues in alpine with system packages that some libraries require suddenly disappearing, unexpectedly moving repos and suddenly becoming an incompatible version. Due to these experiences I found it not stable enough for my own production needs. Maybe it's matured since then idk. You're welcome to make a PR for it. However, this is a discussion I do not wish to have in an issue of another repo, please kindly open an issue in my repo if you wish to further discuss. Cheers.

@sskorol
Copy link

sskorol commented Apr 12, 2022

Just in the case if anyone interested in a bit modified @jonashaag script for Django:

import argparse
from os import environ

import bjoern

from django.core.wsgi import get_wsgi_application
from pathos.multiprocessing import ProcessingPool as Pool

environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings")
application = get_wsgi_application()


def run_worker(port):
    print(f"Starting worker on port {port}...")
    bjoern.run(application, '0.0.0.0', port, reuse_port=True)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--port", "-p", type=int, default=8000)
    parser.add_argument("--workers", "-w", type=int, default=1)
    args = parser.parse_args()

    pool = Pool(nodes=args.workers)
    try:
        pool.map(run_worker, [args.port] * args.workers)
    except KeyboardInterrupt:
        pool.terminate()
        pool.join()
    finally:
        print("Exiting...")


if __name__ == "__main__":
    main()

Note that a pathos fork of a multithreading package was used here instead of a native one.

@tintin10q
Copy link

tintin10q commented May 31, 2022

Could you add instructions as to how to use the docker image? The pip install is not working for me. The pip install is working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants