From 12d09852da82a4a07d780a5fd77e863fa349b07c Mon Sep 17 00:00:00 2001 From: Scott Wallace Date: Sat, 10 Oct 2020 14:11:43 +0100 Subject: [PATCH] Initial code check-in --- .dockerignore | 25 ++++++++++ Dockerfile | 27 ++++++++++ dockstat.py | 126 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + 4 files changed, 180 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 dockstat.py create mode 100644 requirements.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5be8957 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/__pycache__ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e2dd0ef --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# For more information, please refer to https://aka.ms/vscode-docker-python +FROM python:3.8-slim-buster + +# Keeps Python from generating .pyc files in the container +ENV PYTHONDONTWRITEBYTECODE 1 + +# Turns off buffering for easier container logging +ENV PYTHONUNBUFFERED 1 + +# Install pip requirements +ADD requirements.txt . +RUN python -m pip install -r requirements.txt + +WORKDIR /app +ADD . /app + +# Switching to a non-root user, please refer to https://aka.ms/vscode-docker-python-user-rights +# RUN useradd appuser && chown -R appuser /app +# USER appuser + +EXPOSE 8080 + +# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug +CMD ["python", "dockstat.py"] + +HEALTHCHECK --interval=30s --timeout=3s --retries=1 \ + CMD python3 /app/dockstat.py --healthcheck diff --git a/dockstat.py b/dockstat.py new file mode 100644 index 0000000..2794052 --- /dev/null +++ b/dockstat.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +Module to act as a Prometheus Exporter for Docker containers with a + healthcheck configured +""" + +import argparse +import http.server +import os.path +import socketserver +import sys +import time + +import docker + +LISTEN_PORT = 8080 +HEALTHY_STR = 'healthy' + + +class SimpleHTTPRequestHandler(http.server.BaseHTTPRequestHandler): + """ + Class to encompass the requirements of a Prometheus Exporter + for Docker containers with a healthcheck configured + """ + # pylint: disable=invalid-name + def do_GET(self): + """ + Method to handle GET requests + """ + if self.path == '/metrics': + self._metrics() + + if self.path == '/healthcheck': + self._healthcheck() + + + def _healthcheck(self, message=True): + """ + Method to return 200 or 500 response and an optional message + """ + if not healthy(): + self.send_response(500) + self.end_headers() + if message: + self.wfile.write(b'ERR') + return + + self.send_response(200) + self.end_headers() + if message: + self.wfile.write(b'OK') + + + def _metrics(self): + """ + Method to handle the request for metrics + """ + self._healthcheck(message=False) + + api = docker.APIClient() + + client = docker.from_env() + for container in client.containers.list(): + now = int(round(time.time() * 1000)) + data = api.inspect_container(container.id) + + try: + health_str = data["State"]["Health"]["Status"] + except KeyError: + pass + else: + self.wfile.write( + bytes( + f'container_inspect_state_health_status{{' + f'id="{container.id}",' + f'name="{container.name}",' + f'value="{health_str}"' + f'}} ' + f'{int(health_str == HEALTHY_STR)} {now}\n'.encode() + ) + ) + + +def healthy(): + """ + Simple funtion to return if all the requirements are met + """ + return all([ + os.path.exists('/var/run/docker.sock'), + ]) + + +if __name__ == '__main__': + def cli_parse(): + """ + Function to parse the CLI + """ + parser = argparse.ArgumentParser() + + parser.add_argument( + '-H', '--healthcheck', + action='store_true', + help='Simply exit with 0 for healthy or 1 when unhealthy', + ) + + return parser.parse_args() + + + def main(): + """ + main() + """ + args = cli_parse() + + if args.healthcheck: + # Invert the sense of 'healthy' for Unix CLI usage + return not healthy() + + Handler = SimpleHTTPRequestHandler + + with socketserver.TCPServer(('', LISTEN_PORT), Handler) as httpd: + httpd.serve_forever() + + return True + + sys.exit(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1a6c338 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file +docker