Skip to content

Commit

Permalink
Add docker-compose and base dockerfile for development (celery#4482)
Browse files Browse the repository at this point in the history
* Add docker-compose and base dockerfile for development

* Change to base jessie docker image and setup pyenv

* Add in aliases for pyenv, fix entrypoint for docker

* Update dockerfile to be non-root and fix encoding problems with tox

* Add convenience method to get redis connection for tests

* Add pypy to install commands

* Force worker to pick up broker url from environment

* Move pypy comment to above apt-get install, default python to 3.6, add in flag to prevent bytecode

* Add documentation

* Fix links

* Update docs

* Add to contributors

* Address feedback: improve documentation, separate dockerfile into scripts, remove redundancy in pyenv setup, add in .env file

* Setup pyenv environments correctly in dockerfile

* Update capitalization

* Change CELERY_USER to ARG in dockerfile and pass build argument in build

* Change default worker loglevel to debug in docker-compose
  • Loading branch information
Chris7 authored and georgepsarakis committed Mar 18, 2018
1 parent 9ac3e37 commit 59d1400
Show file tree
Hide file tree
Showing 14 changed files with 206 additions and 13 deletions.
49 changes: 49 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,55 @@ fetch and checkout a remote branch like this::
https://notes.envato.com/developers/rebasing-merge-commits-in-git/
.. _`Rebase`: https://help.github.com/rebase/

.. _contributing-docker-development:

Developing and Testing with Docker
----------------------------------

Because of the many components of Celery, such as a broker and backend,
`Docker`_ and `docker-compose`_ can be utilized to greatly simplify the
development and testing cycle. The Docker configuration here requires a
Docker version of at least 17.09.

The Docker components can be found within the :file:`docker/` folder and the
Docker image can be built via:

.. code-block:: console
$ docker-compose build celery
and run via:

.. code-block:: console
$ docker-compose run --rm celery <command>
where <command> is a command to execute in a Docker container. The `--rm` flag
indicates that the container should be removed after it is exited and is useful
to prevent accumulation of unwanted containers.

Some useful commands to run:

* ``bash``

To enter the Docker container like a normal shell

* ``make test``

To run the test suite

* ``tox``

To run tox and test against a variety of configurations

By default, docker-compose will mount the Celery and test folders in the Docker
container, allowing code changes and testing to be immediately visible inside
the Docker container. Environment variables, such as the broker and backend to
use are also defined in the :file:`docker/docker-compose.yml` file.

.. _`Docker`: https://www.docker.com/
.. _`docker-compose`: https://docs.docker.com/compose/

.. _contributing-testing:

Running the unit test suite
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,4 @@ Mikhail Wolfson, 2017/12/11
Alex Garel, 2018/01/04
Régis Behmo 2018/01/20
Igor Kasianov, 2018/01/20
Chris Mitchell, 2018/02/27
2 changes: 1 addition & 1 deletion celery/contrib/testing/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def _start_worker_thread(app,
setup_app_for_worker(app, loglevel, logfile)
assert 'celery.ping' in app.tasks
# Make sure we can connect to the broker
with app.connection() as conn:
with app.connection(hostname=os.environ.get('TEST_BROKER')) as conn:
conn.default_channel.queue_declare

worker = WorkController(
Expand Down
1 change: 1 addition & 0 deletions docker/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CELERY_USER=developer
78 changes: 78 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
FROM debian:jessie

ENV PYTHONIOENCODING UTF-8

# Pypy is installed from a package manager because it takes so long to build.
RUN apt-get update && apt-get install -y \
build-essential \
curl \
git \
libbz2-dev \
libcurl4-openssl-dev \
libmemcached-dev \
libncurses5-dev \
libreadline-dev \
libsqlite3-dev \
libssl-dev \
pkg-config \
pypy \
wget \
zlib1g-dev

# Setup variables. Even though changing these may cause unnecessary invalidation of
# unrelated elements, grouping them together makes the Dockerfile read better.
ENV PROVISIONING /provisioning

# This is provisioned from .env
ARG CELERY_USER=developer

# Check for mandatory build arguments
RUN : "${CELERY_USER:?CELERY_USER build argument needs to be set and non-empty.}"

ENV HOME /home/$CELERY_USER
ENV PATH="$HOME/.pyenv/bin:$PATH"

# Copy and run setup scripts
WORKDIR $PROVISIONING
COPY docker/scripts/install-couchbase.sh .
# Scripts will lose thier executable flags on copy. To avoid the extra instructions
# we call the shell directly.
RUN sh install-couchbase.sh
COPY docker/scripts/create-linux-user.sh .
RUN sh create-linux-user.sh

# Swap to the celery user so packages and celery are not installed as root.
USER $CELERY_USER

COPY docker/scripts/install-pyenv.sh .
RUN sh install-pyenv.sh

# Install celery
WORKDIR $HOME
COPY --chown=1000:1000 requirements $HOME/requirements
COPY --chown=1000:1000 docker/entrypoint /entrypoint
RUN chmod gu+x /entrypoint

# Define the local pyenvs
RUN pyenv local python2.7 python3.4 python3.5 python3.6

# Setup one celery environment for basic development use
RUN pyenv exec pip install \
-r requirements/default.txt \
-r requirements/pkgutils.txt \
-r requirements/test.txt \
-r requirements/test-ci-base.txt \
-r requirements/test-integration.txt

COPY --chown=1000:1000 MANIFEST.in Makefile setup.py setup.cfg tox.ini $HOME/
COPY --chown=1000:1000 t $HOME/t
COPY --chown=1000:1000 celery $HOME/celery

RUN pyenv exec pip install -e .

# the compiled files from earlier steps will cause py.test to fail with
# an ImportMismatchError
RUN make clean-pyc

# Setup the entrypoint, this ensures pyenv is initialized when a container is started.
ENTRYPOINT ["/entrypoint"]
36 changes: 36 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: '2'

services:
celery:
build:
context: ..
dockerfile: docker/Dockerfile
args:
CELERY_USER:
environment:
TEST_BROKER: pyamqp://rabbit:5672
TEST_BACKEND: redis://redis
PYTHONUNBUFFERED: 1
PYTHONDONTWRITEBYTECODE: 1
REDIS_HOST: redis
WORKER_LOGLEVEL: DEBUG
tty: true
volumes:
- ../celery:/home/$CELERY_USER/celery
# Because pytest fails when it encounters files from alternative python compilations,
# __pycache__ and pyc files, PYTHONDONTWRITEBYTECODE must be
# set on the host as well or py.test will throw configuration errors.
# - ../t:/home/$CELERY_USER/t
depends_on:
- rabbit
- redis
- dynamodb

rabbit:
image: rabbitmq:3.7.3

redis:
image: redis:3.2.11

dynamodb:
image: dwmkerr/dynamodb:38
4 changes: 4 additions & 0 deletions docker/entrypoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
exec "$@"
3 changes: 3 additions & 0 deletions docker/scripts/create-linux-user.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
addgroup --gid 1000 $CELERY_USER
adduser --system --disabled-password --uid 1000 --gid 1000 $CELERY_USER
5 changes: 5 additions & 0 deletions docker/scripts/install-couchbase.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
wget http://packages.couchbase.com/clients/c/libcouchbase-2.8.4_jessie_amd64.tar
tar -vxf libcouchbase-2.8.4_jessie_amd64.tar
dpkg -i libcouchbase-2.8.4_jessie_amd64/libcouchbase2-core_2.8.4-1_amd64.deb
dpkg -i libcouchbase-2.8.4_jessie_amd64/libcouchbase-dev_2.8.4-1_amd64.deb
13 changes: 13 additions & 0 deletions docker/scripts/install-pyenv.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
# For managing all the local python installations for testing, use pyenv
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash

# To enable testing versions like 3.4.8 as 3.4 in tox, we need to alias
# pyenv python versions
git clone https://github.com/s1341/pyenv-alias.git $(pyenv root)/plugins/pyenv-alias

# Python versions to test against
VERSION_ALIAS="python2.7" pyenv install 2.7.14
VERSION_ALIAS="python3.4" pyenv install 3.4.8
VERSION_ALIAS="python3.5" pyenv install 3.5.5
VERSION_ALIAS="python3.6" pyenv install 3.6.4
5 changes: 5 additions & 0 deletions t/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def _inner(*args, **kwargs):
return _inner


def get_redis_connection():
from redis import StrictRedis
return StrictRedis(host=os.environ.get('REDIS_HOST'))


@pytest.fixture(scope='session')
def celery_config():
return {
Expand Down
12 changes: 5 additions & 7 deletions t/integration/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from celery.exceptions import SoftTimeLimitExceeded
from celery.utils.log import get_task_logger

from .conftest import get_redis_connection

logger = get_task_logger(__name__)


Expand Down Expand Up @@ -115,17 +117,15 @@ def retry_once(self):
@shared_task
def redis_echo(message):
"""Task that appends the message to a redis list"""
from redis import StrictRedis

redis_connection = StrictRedis()
redis_connection = get_redis_connection()
redis_connection.rpush('redis-echo', message)


@shared_task(bind=True)
def second_order_replace1(self, state=False):
from redis import StrictRedis

redis_connection = StrictRedis()
redis_connection = get_redis_connection()
if not state:
redis_connection.rpush('redis-echo', 'In A')
new_task = chain(second_order_replace2.s(),
Expand All @@ -137,9 +137,7 @@ def second_order_replace1(self, state=False):

@shared_task(bind=True)
def second_order_replace2(self, state=False):
from redis import StrictRedis

redis_connection = StrictRedis()
redis_connection = get_redis_connection()
if not state:
redis_connection.rpush('redis-echo', 'In B')
new_task = chain(redis_echo.s("In/Out C"),
Expand Down
9 changes: 4 additions & 5 deletions t/integration/test_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
from time import sleep

import pytest
from redis import StrictRedis

from celery import chain, chord, group
from celery.exceptions import TimeoutError
from celery.result import AsyncResult, GroupResult

from .conftest import flaky
from .conftest import flaky, get_redis_connection
from .tasks import (add, add_chord_to_chord, add_replaced, add_to_all,
add_to_all_to_chord, collect_ids, delayed_sum,
delayed_sum_with_soft_guard, identity, ids, print_unicode,
Expand Down Expand Up @@ -66,7 +65,7 @@ def test_group_chord_group_chain(self, manager):

if not manager.app.conf.result_backend.startswith('redis'):
raise pytest.skip('Requires redis result backend.')
redis_connection = StrictRedis()
redis_connection = get_redis_connection()
redis_connection.delete('redis-echo')
before = group(redis_echo.si('before {}'.format(i)) for i in range(3))
connect = redis_echo.si('connect')
Expand Down Expand Up @@ -94,7 +93,7 @@ def test_second_order_replace(self, manager):
if not manager.app.conf.result_backend.startswith('redis'):
raise pytest.skip('Requires redis result backend.')

redis_connection = StrictRedis()
redis_connection = get_redis_connection()
redis_connection.delete('redis-echo')

result = second_order_replace1.delay()
Expand Down Expand Up @@ -242,7 +241,7 @@ def test_redis_subscribed_channels_leak(self, manager):
if not manager.app.conf.result_backend.startswith('redis'):
raise pytest.skip('Requires redis result backend.')

redis_client = StrictRedis()
redis_client = get_redis_connection()
async_result = chord([add.s(5, 6), add.s(6, 7)])(delayed_sum.s())
for _ in range(TIMEOUT):
if async_result.state == 'STARTED':
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ commands =
integration: py.test -xsv t/integration
setenv =
WORKER_LOGLEVEL = INFO
PYTHONIOENCODING = UTF-8

rabbitmq: TEST_BROKER=pyamqp://
rabbitmq: TEST_BACKEND=rpc
Expand Down

0 comments on commit 59d1400

Please sign in to comment.