Sidecar container for Python exceptions

Posted by Martin Kutlak on April 19, 2018

In recent blog posts container-exception-logger (CEL) and catching unhandled Python exceptions we’ve introduced new tools for containers. Adding the tools to your Fedora container allows you to catch Python exceptions in the container and get logs of them to the host.

If however your containers do not run an RPM-based Linux distribution or you don’t want to rebuild your image, you can utilize a sidecar container and get the above-mentioned tools into your container through it.

Sidecar container

The sidecar container should contain Python hooks and the CEL executable. You can use this script that will download Python hooks and the CEL from the Github and build the CEL executable from sources.

#!/bin/sh

RAWGITHUB="https://raw.githubusercontent.com/abrt/abrt/master/src/hooks"

# Python2 hooks
mkdir -p abrt-hooks/py2
wget -qN "$RAWGITHUB/abrt_container.pth" -P abrt-hooks/py2
wget -qN "$RAWGITHUB/abrt_exception_handler_container.py" -P abrt-hooks/py2

# Python3 hooks
mkdir -p abrt-hooks/py3
wget -qN "$RAWGITHUB/abrt3_container.pth" -P abrt-hooks/py3
wget -qN "$RAWGITHUB/abrt_exception_handler3_container.py" -P abrt-hooks/py3

# Binary container-exception-logger
mkdir -p abrt-hooks/bin
BASEDIR=$(pwd)
TMPDIR=$(mktemp -d)
pushd $TMPDIR
git clone https://github.com/abrt/container-exception-logger.git abrt-cel ; cd abrt-cel
make
cp src/container-exception-logger $BASEDIR/abrt-hooks/bin
popd

echo "Done"

Example of Dockerfile for the sidecar container with the Python hooks and CEL.

FROM alpine:latest

RUN mkdir -p /abrt/bin /tmp/abrt-hooks

# Copy hook files
# abrt-hooks/
# ├── bin
# │  └── container-exception-logger
# ├── py2
# │  ├── abrt_container.pth
# │  └── abrt_exception_handler_container.py
# ├── py3
# │  ├── abrt3_container.pth
# │  └── abrt_exception_handler3_container.py
# └── usercustomize.py
COPY abrt-hooks /tmp/abrt-hooks

# Copy script
# files/
# └── usr
#    └── local
#       └── bin
#          └── abrt-cp.sh
COPY files /

VOLUME ["/abrt"]
RUN chmod -R 777 /abrt /tmp/abrt-hooks
CMD [ "/bin/sh", "/usr/local/bin/abrt-cp.sh" ]

The sidecar container includes a simple script that is run when the container starts. The script just copies files to a shared volume and then goes into a sleep loop.

# abrt-cp.sh
#!/bin/sh
cp -R /tmp/abrt-hooks/* /abrt
while true; do sleep 300; done

Another important part that is in the sidecar container is usercustomize module. The module takes care of adding directories with Python hooks to a sys.path.

# usercustomize.py
import os
import site
from sys import version_info

hook_path = os.path.dirname(os.path.abspath(__file__))
if version_info[0] == 2:
    site.addsitedir(os.path.join(hook_path, "py2"))
else:
    site.addsitedir(os.path.join(hook_path, "py3"))

Setting up the container

First, we get current values of PATH and PYTHONPATH variables in our main container. The PYTHONPATH variable is used to import the usercustomize module; the PATH is needed for the CEL executable.

$ oc exec <pod> -it -- sh -c 'echo PATH: $PATH; echo PYTHONPATH: $PYTHONPATH'
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PYTHONPATH:

Once we know the values we can append to them paths of Python hooks and the CEL executable. It is possible to skip this step and edit the variables manually in the deployment config.

$ oc env -c <container> dc/<name> PYTHONPATH=/abrt \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/abrt/bin
deploymentconfig "<name>" updated

In the deployment config, we add the sidecar container and share the volume between the two containers. We can also edit the PATH, PYTHONPATH variables here.

$ oc edit dc/<name>;
spec:
  containers:
  - name: main_container
    env:
    - name: PYTHONPATH
      value: /abrt
    - name: PATH
      value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/abrt/bin
    ...
    volumeMount:
    - name: abrt-volume
      path: /abrt
  ...
  - name: sidecar
    ...
    volumeMount:
    - name: abrt-volume
      path: /abrt
  ...
  volumes:
  - emptyDir: {}
    name: abrt-volume
  ...

Once the deployment finishes, we can check whether the path to the hooks was added to site directories.

$ python2 -m site
sys.path = [
    '/',
    '/abrt',
    '/abrt/py2',
    '/usr/lib/python2.7',
    '/usr/lib/python2.7/plat-x86_64-linux-gnu',
    '/usr/lib/python2.7/lib-tk',
    '/usr/lib/python2.7/lib-old',
    '/usr/lib/python2.7/lib-dynload',
    '/usr/local/lib/python2.7/dist-packages',
    '/usr/lib/python2.7/dist-packages',
]
USER_BASE: '/.local' (doesn't exist)
USER_SITE: '/.local/lib/python2.7/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

Now that we see the path was added correctly we can test if the hooks work. To do that we will cause an exception in our main container.

$ python2 -c 0/0
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

If we check the journal log on the host we should be able to see the error message.

$ journalctl -xe
Apr 3 14:15:92 work dockerd-current[1175]: container-exception-logger - {
"executable": "python -c ...",
"backtrace": "<string>:1:<module>:ZeroDivisionError: integer division or modulo by zero
Traceback (most recent call last):
File \"<string>\", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
Local variables in innermost frame:
__builtins__: <module '__builtin__' (built-in)>
__name__: '__main__'
__doc__: None
__package__: None",
"pid": 36,
"reason": "<string>:1:<module>:ZeroDivisionError: integer division or modulo by zero",
"time": "6535897932",
"type": "Python"}