Containerizing a Flask microservice in Kubernetes

4/20/2020

I've been working on a Kubernetes cluster with microservices written in Flask for some time now and I'm not sure if my current method for containerizing them is correct.

I've been using this image as a base.

But I've been seeing various posts saying that something like that may be a bit overkill.

The problem I have is that whenever I look up an article about using Flask with Kubernetes, they always skip over the details of the actual container and focus on building the cluster, which is something I already have a pretty solid handle on. I guess what I'm wondering is whether there's a better way to build a docker image for a single Flask app because it's hard to find a straight answer.

-- theelk801
docker
flask
kubernetes
microservices
python

2 Answers

5/26/2020

I got your point you are thinking you docker image creation method may be wrong.

The main idea while building docker image. The image should have only your dependencies. As you told to find an answer is hard because we don't know your requirement maybe your dockerfile is only way.

I recommend you to this document. I read this document and really my docker images is improved. I think it will help you.

https://drive.google.com/file/d/16t_-DRTohzyVPJy6Cx8a3PxLQ-95CfYK/view

When I checked you dockerfile; I noticed only, I would like to share you;

https://github.com/tiangolo/uwsgi-nginx-flask-docker/blob/master/docker-images/python3.7.dockerfile#L5

Line 5th It should not be perform like that. The dockerfile should not be updated frequently and each dependency should be versioned.

I build this image like that.

I write all dependencies to req.txt with version.

req.txt

flask==1.1.1

Dockerfile

+ COPY req.txt .
+ RUN pip install -r req.txt

Also I would like to perform line between 5 and 28 in a bash script.

An Important Information; My manager don't want comment line in a Dockerfile. She wants to understand when she read it. She wants to simple and readable docker file. :) She is a wiser. You should keep simple your dockerfile and it should be understand whitout any comment lines.

-- Aziz F Dagli
Source: StackOverflow

4/20/2020

"better" is entirely relative, but here is the one I use.

FROM python:3.7 AS build

ENV PYTHONFAULTHANDLER=1 \
    PYTHONUNBUFFERED=1 \
    PYTHONHASHSEED=random \
    PIP_NO_CACHE_DIR=off \
    PIP_DISABLE_PIP_VERSION_CHECK=on \
    PIP_DEFAULT_TIMEOUT=100

RUN pip install poetry==1.0.5

WORKDIR /app
COPY poetry.lock pyproject.toml /app/
RUN poetry config virtualenvs.create false && \
    poetry install --no-dev --no-interaction --no-ansi


FROM gcr.io/distroless/python3-debian10

WORKDIR /app
ENV PYTHONPATH=/usr/local/lib/python3.7/site-packages/
COPY --from=build /usr/local/lib/python3.7/site-packages/ /usr/local/lib/python3.7/site-packages/
COPY . /app

CMD ["-m", "myapp"]

With that -m entrypoint looking like:

from . import create_app

application = create_app()


def main() -> None:
    import sys
    from twisted import logger  # type: ignore
    from twisted.internet import reactor  # type: ignore
    from twisted.internet.endpoints import TCP4ServerEndpoint  # type: ignore
    from twisted.python import threadpool  # type: ignore
    from twisted.web.server import Site  # type: ignore
    from twisted.web.wsgi import WSGIResource  # type: ignore
    from prometheus_client.twisted import MetricsResource  # type: ignore

    observers = [logger.textFileLogObserver(sys.stdout)]
    logger.globalLogBeginner.beginLoggingTo(observers)
    logger.Logger().info("myapp starting on :8000")

    pool = threadpool.ThreadPool()
    reactor.callWhenRunning(pool.start)
    django_resource = WSGIResource(reactor, pool, application)
    django_site = Site(django_resource)
    django_endpoint = TCP4ServerEndpoint(reactor, 8000)
    django_endpoint.listen(django_site)
    metrics_resource = MetricsResource()
    metrics_site = Site(metrics_resource)
    metrics_endpoint = TCP4ServerEndpoint(reactor, 9000)
    metrics_endpoint.listen(metrics_site)
    reactor.run()
    pool.stop()


if __name__ == "__main__":
    main()
-- coderanger
Source: StackOverflow