Skip to content

Commit

Permalink
First version of Dockerfile & docker-compose (#416)
Browse files Browse the repository at this point in the history
* First version of Dockerfile & docker-compose, including a health check in the FM API

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* fix inability to call flexmeasures command in container (had to use flask), by not installing in editable mode

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* load the instance directory as volume, which allows to pass a configuration file; use flexmeasures run as default command for now

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* document better & correctly how to mount config file into a FlexMeasures container, clean up docs

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* copy less unneeded files into FlexMeasures image

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* run the FlexMeasures image via Gunicorn

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* install gunicorn early on

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* add changelog entries

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* mention the Docker image on index and in toy tutorial section

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* move the note about using Docker for the tutorial a little lower

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* install solver as well

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* document how at this point one can run tests inside a FlexMeasures container

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* load sql extensions into postgres service

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* add another note to the preliminary testing setup

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* Run tests easily in Docker: add test-db service in compose stack, and a way to tell FlexMeasures to use a different database URI than the standard one when testing

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* improve documentation, from review comments

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* fix typo

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* lazy loading in SensorIdField (fixes flexmeasures add schedule)

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* docs: add FLASK_ENV=development to docker run call

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* split docker and docker-compose docs (first is relevant for installing/deploying), the latter more for development. Also fix small errors and give better info on WSGI scripts.

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* fix regression in not attaching account to assets correctly in toy tutorial

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* separate installation instructions for toy tutorial into Docker and non-Docker

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* some text improvements

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* correct the link in coverage badge, to report on main branch

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* switch to lfenergy/flexmeasures

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* small comments from review

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* demanding the lowest docker compose version we know we need (we use start_period for healthchecks)

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* more attention-grabbing note about the tutorial happening inside the docker container

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* not only the API runs in the container

Signed-off-by: Nicolas Höning <nicolas@seita.nl>

* better name for the Docker container in the compose tutorial & smaller doc fixes

Signed-off-by: Nicolas Höning <nicolas@seita.nl>
  • Loading branch information
nhoening committed Apr 25, 2022
1 parent 66b6e49 commit 4cb6359
Show file tree
Hide file tree
Showing 27 changed files with 451 additions and 42 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -16,6 +16,7 @@ venv
.env-flexmeasures/
flexmeasures.egg-info
flexmeasures.log*
*.csv

.cache
__pycache__
Expand Down
42 changes: 42 additions & 0 deletions Dockerfile
@@ -0,0 +1,42 @@
FROM ubuntu:focal

# TODO: Cbc solver
# TODO: run gunicorn as entry command

ENV DEBIAN_FRONTEND noninteractive
ENV LC_ALL C.UTF-8
ENV LANG C.UTF-8

# pre-requisites
RUN apt-get update && apt-get install -y --upgrade python3 python3-pip git curl gunicorn coinor-cbc

WORKDIR /app
# requirements - doing this earlier, so we don't install them each time. Use --no-cache to refresh them.
COPY requirements /app/requirements

# py dev tooling
RUN python3 -m pip install --upgrade pip && python3 --version
RUN pip3 install --upgrade setuptools
RUN pip3 install -r requirements/app.txt -r requirements/dev.txt -r requirements/test.txt

# Copy code and meta/config data
COPY setup.* .flaskenv wsgi.py /app/
COPY flexmeasures/ /app/flexmeasures
RUN find . | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm -rf
COPY .git/ /app/.git

RUN pip3 install .

EXPOSE 5000

CMD [ \
"gunicorn", \
"--bind", "0.0.0.0:5000", \
# This is set to /tmp by default, but this is part of the Docker overlay filesystem, and can cause stalls.
# http://docs.gunicorn.org/en/latest/faq.html#how-do-i-avoid-gunicorn-excessively-blocking-in-os-fchmod
"--worker-tmp-dir", "/dev/shm", \
# Ideally you'd want one worker per container, but we don't want to risk the health check timing out because
# another request is taking a long time to complete.
"--workers", "2", "--threads", "4", \
"wsgi:application" \
]
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -18,7 +18,7 @@ update-docs:
@echo "Creating docs environment ..."
make install-docs-dependencies
@echo "Creating documentation ..."
cd documentation; make clean; make html; cd ..
cd documentation; make clean; make html SPHINXOPTS="-W --keep-going -n"; cd ..

update-docs-pdf:
@echo "NOTE: PDF documentation requires packages (on Debian: latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended)"
Expand Down
4 changes: 2 additions & 2 deletions Readme.md
Expand Up @@ -6,7 +6,7 @@
[![](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Documentation Status](https://readthedocs.org/projects/flexmeasures/badge/?version=latest)](https://flexmeasures.readthedocs.io/en/latest/?badge=latest)
[![Coverage](https://coveralls.io/repos/github/FlexMeasures/flexmeasures/badge.svg?branch=coverage-in-ci)](https://coveralls.io/github/FlexMeasures/flexmeasures?branch=coverage-in-ci)
[![Coverage](https://coveralls.io/repos/github/FlexMeasures/flexmeasures/badge.svg)](https://coveralls.io/github/FlexMeasures/flexmeasures)

The *FlexMeasures Platform* is the intelligent EMS (energy management system) to support real-time energy flexibility apps, rapidly and scalable.

Expand Down Expand Up @@ -42,4 +42,4 @@ We made FlexMeasures freely available under the Apache2.0 licence and it is now

Within the FlexMeasures project, [we welcome contributions](https://github.com/FlexMeasures/tsc/blob/main/CONTRIBUTING.md). You can also [learn more about our governance](https://github.com/Flexmeasures/tsc/blob/main/GOVERNANCE.md).

You can connect with the community here on Github (e.g. by creating an issue), on [the mailing list](https://lists.lfenergy.org/g/flexmeasures), on [the FlexMeasures channel within the LF Energy Slack](https://slack.lfenergy.org/) or [by contacting the current maintainers](https://www.seita.nl/contact).
You can connect with the community here on Github (e.g. by creating an issue), on [the mailing list](https://lists.lfenergy.org/g/flexmeasures), on [the FlexMeasures channel within the LF Energy Slack](https://slack.lfenergy.org/) or [by contacting the current maintainers](https://www.seita.nl/contact).
2 changes: 2 additions & 0 deletions ci/load-psql-extensions.sql
@@ -0,0 +1,2 @@
CREATE EXTENSION IF NOT EXISTS cube;
CREATE EXTENSION IF NOT EXISTS earthdistance;
2 changes: 1 addition & 1 deletion ci/setup-postgres.sh
Expand Up @@ -27,4 +27,4 @@ while [[ true ]]; do
fi
done

psql -h $PGHOST -p $PGPORT -c "create extension if not exists cube; create extension if not exists earthdistance;" -U $PGUSER $PGDB;
psql -h $PGHOST -p $PGPORT --file ci/load-psql-extensions.sql -U $PGUSER $PGDB;
63 changes: 63 additions & 0 deletions docker-compose.yml
@@ -0,0 +1,63 @@
version: "3.4"

# -----------------------------------------------
# TODO:
# * Add redis service
# * Add a FlexMeasures worker service, maybe using https://docs.docker.com/compose/compose-file/#entrypoint
# -----------------------------------------------

services:
dev-db:
image: postgres
expose:
- 5432
restart: always
environment:
POSTGRES_DB: fm-dev-db
POSTGRES_USER: fm-dev-db-user
POSTGRES_PASSWORD: fm-dev-db-pass
volumes:
- ./ci/load-psql-extensions.sql:/docker-entrypoint-initdb.d/load-psql-extensions.sql
server:
build:
context: .
dockerfile: Dockerfile
ports:
- 5000:5000
depends_on:
- dev-db
- test-db # use -e SQLALCHEMY_TEST_DATABASE_URI=... to exec pytest
restart: on-failure
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/api/v3_0/health/ready"]
start_period: 10s
interval: 20s
timeout: 10s
retries: 6
environment:
SQLALCHEMY_DATABASE_URI: "postgresql://fm-dev-db-user:fm-dev-db-pass@dev-db:5432/fm-dev-db"
SECRET_KEY: notsecret
FLASK_ENV: development
LOGGING_LEVEL: INFO
volumes:
# a place for config and plugin code - the mount point is for running the FlexMeasures CLI, the 2nd for gunicorn
- ./flexmeasures-instance/:/usr/var/flexmeasures-instance/:ro
- ./flexmeasures-instance/:/app/instance/:ro
command: >
bash -c "flexmeasures db upgrade
&& flexmeasures add toy-account --name 'Docker Toy Account'
&& gunicorn --bind 0.0.0.0:5000 --worker-tmp-dir /dev/shm --workers 2 --threads 4 wsgi:application"
test-db:
image: postgres
expose:
- 5432
restart: always
environment:
POSTGRES_DB: fm-test-db
POSTGRES_USER: fm-test-db-user
POSTGRES_PASSWORD: fm-test-db-pass
volumes:
- ./ci/load-psql-extensions.sql:/docker-entrypoint-initdb.d/load-psql-extensions.sql

volumes:
flexmeasures-instance:
2 changes: 2 additions & 0 deletions documentation/changelog.rst
Expand Up @@ -9,12 +9,14 @@ v0.10.0 | April XX, 2022
New features
-----------
* Improve legibility of chart axes [see `PR #413 <http://www.github.com/FlexMeasures/flexmeasures/pull/413>`_]
* API provides health readiness check at /api/v3_0/health/ready [see `PR #416 <http://www.github.com/FlexMeasures/flexmeasures/pull/416>`_]

Bugfixes
-----------

Infrastructure / Support
----------------------
* Dockerfile to run FlexMeasures in container; also docker-compose file [see `PR #416 <http://www.github.com/FlexMeasures/flexmeasures/pull/416>`_]
* Unit conversion prefers shorter units in general [see `PR #415 <http://www.github.com/FlexMeasures/flexmeasures/pull/415>`_]
* Shorter CI builds in Github Actions by caching Python environment [see `PR #361 <http://www.github.com/FlexMeasures/flexmeasures/pull/361>`_]
* Allow to filter data by source using a tuple instead of a list [see `PR #421 <http://www.github.com/FlexMeasures/flexmeasures/pull/421>`_]
Expand Down
5 changes: 3 additions & 2 deletions documentation/conf.py
Expand Up @@ -48,6 +48,7 @@
"sphinx.ext.ifconfig",
"sphinx.ext.todo",
"sphinx_copybutton",
"sphinx_tabs.tabs",
"sphinx_fontawesome",
"sphinxcontrib.autohttp.flask",
"sphinxcontrib.autohttp.flaskqref",
Expand Down Expand Up @@ -77,7 +78,7 @@
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

# Todo: these are not mature enough yet for release
# Todo: these are not mature enough yet for release, or should be removed
exclude_patterns.append("int/*.rst")
exclude_patterns.append("concepts/assets.rst")
exclude_patterns.append("concepts/markets.rst")
Expand Down Expand Up @@ -194,7 +195,7 @@
# -- Options for intersphinx extension ---------------------------------------

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {"https://docs.python.org/": None}
intersphinx_mapping = {"https://docs.python.org/3/": None}

# -- Options for copybytton extension ---------------------------------------
copybutton_prompt_is_regexp = True
Expand Down
12 changes: 11 additions & 1 deletion documentation/configuration.rst
Expand Up @@ -6,7 +6,7 @@ Configuration
The following configurations are used by FlexMeasures.

Required settings (e.g. postgres db) are marked with a double star (**).
To enable easier quickstart tutorials, these settings can be set by environment variables.
To enable easier quickstart tutorials, these required settings can be set by environment variables.
Recommended settings (e.g. mail, redis) are marked by one star (*).

.. note:: FlexMeasures is best configured via a config file. The config file for FlexMeasures can be placed in one of two locations:
Expand Down Expand Up @@ -302,6 +302,16 @@ Default:
}
SQLALCHEMY_TEST_DATABASE_URI
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When running tests (``make test``, which runs ``pytest``), the default database URI is set in ``utils.config_defaults.TestingConfig``.
You can use this setting to overwrite that URI and point the tests to an (empty) database of your choice.

.. note:: This setting is only supported as an environment variable, not in a config file, and only during testing.



Security
--------

Expand Down
14 changes: 13 additions & 1 deletion documentation/dev/ci.rst
@@ -1,4 +1,4 @@
.. continuous_integration:
.. _continuous_integration:

Continuous integration
======================
Expand Down Expand Up @@ -97,3 +97,15 @@ The last step, touching a wsgi.py file, is often used as a way to soft-restart t
echo "RESTARTING APPLICATION ..."
touch $PATH_TO_WSGI
A WSGI file can do various things, as well, but the simplest form is shown below.

.. code-block:: python
from flexmeasures.app import create as create_app
application = create_app()
The web server is told about the WSGI script, but also about the object which represents the application. For instance, if this script is called ``wsgi.py``, then the relevant argument to the gunicorn server is ``wsgi:application``.
83 changes: 83 additions & 0 deletions documentation/dev/docker-compose.rst
@@ -0,0 +1,83 @@
.. _docker-compose:

Running a complete stack with docker-compose
=============================================

To install FlexMeasures, plus the libraries and databases it depends on, on your computer is some work, and can have unexpected hurdles, e.g. depending on the operating system. A nice alternative is to let that happen within Docker. The whole stack can be run via `Docker compose <https://docs.docker.com/compose/>`_, saving the developer much time.

For this, we assume you are in the directory housing ``docker-compose.yml``.


.. note:: The minimum Docker version is 17.09 and for docker-compose we tested successfully at version 1.25. You can check your versions with ``docker[-compose] --version``.

Build the compose stack
------------------------

Run this:

.. code-block:: bash
docker-compose build
This pulls the images you need, and re-builds the FlexMeasures one from code. If you change code, re-running this will re-build that image.

This compose script can also serve as an inspiration for using FlexMeasures in modern cloud environments (like Kubernetes). For instance, you might want to not build the FlexMeasures image from code, but simply pull the image from DockerHub.

.. todo:: This stack runs FlexMeasures, but misses the background worker aspect. For this, we'll add a redis node and one additional FlexMeasures node, which runs a worker as entry point instead (see `issue 418 <https://github.com/FlexMeasures/flexmeasures/issues/418>`_).


Run the compose stack
----------------------

Start the stack like this:

.. code-block:: bash
docker-compose up
You can see log output in the terminal, but ``docker-compose logs`` is also available to you.

Check ``docker ps`` or ``docker-compose ps`` to see if your containers are running:


.. code-block:: console
± docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dda1a8606926 flexmeasures_server "bash -c 'flexmeasur…" 43 seconds ago Up 41 seconds (healthy) 0.0.0.0:5000->5000/tcp flexmeasures_server_1
27ed9eef1b04 postgres "docker-entrypoint.s…" 2 days ago Up 42 seconds 5432/tcp flexmeasures_dev-db_1
90df2065e08d postgres "docker-entrypoint.s…" 2 days ago Up 42 seconds 5432/tcp flexmeasures_test-db_1
The FlexMeasures container has a health check implemented, which is reflected in this output and you can see which ports are available on your machine to interact.

You can use ``docker-compose logs`` to look at output. ``docker inspect <container>`` and ``docker exec -it <container> bash`` can be quite useful to dive into details.

.. todo:: We should provide a way to test that this is working, e.g. a list of steps. Document this, but also include that in our tsc/Release list (as a test step to see if Dockerization still works, plus a publish step for the released version).


Configuration
---------------

You can pass in your own configuration (e.g. for MapBox access token, or db URI, see below) like we described above for running a container: put a file ``flexmeasures.cfg`` into a local folder called ``flexmeasures-instance``.


Data
-------

The postgres database is a test database with toy data filled in when the flexmeasures container starts.
You could also connect it to some other database, by setting a different ``SQLALCHEMY_DATABASE_URI`` in the config.


Running tests
---------------

You can run tests in the flexmeasures docker container, using the database service ``test-db`` in the compose file (per default, we are using the ``dev-db`` database service).

After you've started the compose stack with ``docker-compose up``, run:

.. code-block:: console
docker exec -it -e SQLALCHEMY_TEST_DATABASE_URI="postgresql://fm-test-db-user:fm-test-db-pass@test-db:5432/fm-test-db" flexmeasures_server_1 pytest
This rounds up the dev experience offered by running FlexMeasures in Docker. Now you can develop FlexMeasures and also run your tests. If you develop plugins, you could extend the command being used, e.g. ``bash -c "cd /path/to/my/plugin && pytest"``.
2 changes: 1 addition & 1 deletion documentation/host/data.rst
Expand Up @@ -33,7 +33,7 @@ On Unix:

.. code-block:: console
sudo apt-get install postgresql
sudo apt-get install postgresql-12
pip install psycopg2-binary
Expand Down
19 changes: 14 additions & 5 deletions documentation/host/deployment.rst
Expand Up @@ -5,11 +5,12 @@ How to deploy FlexMeasures

Here you can learn how to get FlexMeasures onto a server.

.. note:: FlexMeasures can be deployed via Docker. Read more at :ref:`docker-image`.

.. contents:: Table of contents
:local:
:depth: 1

.. todo:: It would be great to enable Dockerization of FlexMeasures, let us know if this matters to you.


WSGI configuration
Expand All @@ -22,17 +23,25 @@ On your own computer, ``flexmeasures run`` is a nice way to start FlexMeasures.
# This file contains the WSGI configuration required to serve up your
# web application.
# It works by setting the variable 'application' to a WSGI handler of some description.
# The crucial part are the last two lines. We add some ideas for possible other logic.
import os
from dotenv import load_dotenv
project_home = u'/path/to/your/code/flexmeasures'
# use this if you want to load your own ``.env`` file.
from dotenv import load_dotenv
load_dotenv(os.path.join(project_home, '.env'))
# use this if you run from source
if project_home not in sys.path:
sys.path = [project_home] + sys.path
# adapt PATH to find our LP solver if it is installed from source
os.environ["PATH"] = os.environ.get("PATH") + ":/home/seita/Cbc-2.9/bin"
# create flask app - need to call it "application" for WSGI to work
# create flask app - the name "application" has to be passed to the WSGI server
from flexmeasures.app import create as create_app
application = create_app()
The web server is told about the WSGI script, but also about the object which represents the application. For instance, if this script is called ``wsgi.py``, then the relevant argument to the gunicorn server is ``wsgi:application``.

Keep in mind that FlexMeasures is based on `Flask <https://flask.palletsprojects.com/>`_, so almost all knowledge on the web on how to deploy a Flask app also helps with deploying FlexMeasures.


Expand All @@ -52,7 +61,7 @@ You can install it on Debian like this:
If you can't use the package manager on your host, the solver has to be installed from source.
We provide `an example script <ci/install-cbc.sh>`_ to do that, where you can also
We provide an example script in ``ci/install-cbc-from-source.sh`` to do that, where you can also
pass a directory for the installation.

In case you want to install a later version, adapt the version in the script.
Expand Down

0 comments on commit 4cb6359

Please sign in to comment.