Skip to content

Commit

Permalink
Added possibility to monitor pidfile.
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrien Delle Cave committed Jun 23, 2021
1 parent e4e953b commit a696be7
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 90 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
monit-docker (0.0.41) unstable; urgency=medium

* Added possibility to monitor pidfile.

-- Adrien DELLE CAVE (Decryptus) <adrien.delle.cave@commandersact.com> Wed, 23 Jun 2021 12:49:47 +0200

monit-docker (0.0.40) unstable; urgency=medium

* Reviewed requirements.

-- Adrien DELLE CAVE (Decryptus) <adrien.delle.cave@commandersact.com> Sat, 03 Apr 2021 21:08:06 +0200

monit-docker (0.0.39) unstable; urgency=medium

* Reviewed requirements.

-- Adrien DELLE CAVE (Decryptus) <adrien.delle.cave@commandersact.com> Wed, 17 Mar 2021 15:52:00 +0100

monit-docker (0.0.38) unstable; urgency=medium

* Reviewed requirements.
Expand Down
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ See [docker-compose.yml](docker-compose.yml) and MONIT\_DOCKER\_CRONS environmen

## <a name="environment_variables"></a>Environment variables

| Variable | Description | Default |
|:------------------------|:----------------------------|:--------|
| `MONIT_DOCKER_CONFIG` | Configuration file contents<br />(e.g. `export MONIT_DOCKER_CONFIG="$(cat monit-docker.yml)"`) | |
| `MONIT_DOCKER_CONFFILE` | Configuration file path | /etc/monit-docker/monit-docker.yml |
| `MONIT_DOCKER_LOGFILE` | Log file path | /var/log/monit-docker/monit-docker.log |
| Variable | Description | Default |
|:---------------------------|:----------------------------|:--------|
| `MONIT_DOCKER_CONFIG` | Configuration file contents<br />(e.g. `export MONIT_DOCKER_CONFIG="$(cat monit-docker.yml)"`) | |
| `MONIT_DOCKER_CONFFILE` | Configuration file path | /etc/monit-docker/monit-docker.yml |
| `MONIT_DOCKER_LOGFILE` | Log file path | /var/log/monit-docker/monit-docker.log |
| `MONIT_DOCKER_RUNTIMEDIR` | Runtime directory path | /run/monit-docker |

## <a name="sub-command_monit"></a>Sub-command: monit

Expand All @@ -65,6 +66,10 @@ You can also use status argument, for example, restart containers with status pa

`monit-docker -s paused -s exited monit --cmd 'restart'`

Generate containers pidfile:

`monit-docker monit --rsc pid`

Reload php-fpm in container with image name contains /php-fpm/ if memory usage greater than 100 MiB:

`monit-docker --image '*/php-fpm/*' monit --cmd-if 'mem_usage > 100 MiB ? (kill -USR2 1)'`
Expand Down Expand Up @@ -157,6 +162,11 @@ check program docker.foo_php_fpm.mem with path "/usr/bin/monit-docker -s running
if status > 100 for 2 cycles then alert
if status > 70 for 2 cycles then alert
if status > 80 for 4 cycles then exec "/usr/bin/monit-docker --name foo_php_fpm monit --cmd '(kill -USR2 1)'"
check program docker.foo_php_fpm.pid with pidfile /run/monit-docker/foo_php_fpm.pid
group monit-docker
if changed pid then alert
```

## <a name="sub-command_stats"></a>Sub-command: stats
Expand All @@ -178,7 +188,8 @@ Get all resources statistics for all containers in json format:
"io_read": "3.5 MB",
"io_write": "0.0 B",
"net_rx": "25.2 kB",
"mem_limit": "7.27 GiB"
"mem_limit": "7.27 GiB",
"pid": "3943"
}
}
{
Expand All @@ -191,7 +202,8 @@ Get all resources statistics for all containers in json format:
"io_read": "24.6 kB",
"io_write": "0.0 B",
"net_rx": "25.0 kB",
"mem_limit": "7.27 GiB"
"mem_limit": "7.27 GiB",
"pid": "3990"
}
}
```
Expand All @@ -209,4 +221,4 @@ practical_proskuriakova|mem_usage:2.61 MiB|mem_limit:7.27 GiB|mem_percent:0.04|c

Get status and memory usage for group nodejs:

`monit-docker --ctn-group nodejs --rsc status --rsc mem_usage`
`monit-docker --ctn-group nodejs stats --rsc status --rsc mem_usage`
2 changes: 1 addition & 1 deletion RELEASE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.38
0.0.41
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.38
0.0.41
187 changes: 112 additions & 75 deletions bin/monit-docker
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env python
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright 2019 Adrien Delle Cave
# Copyright 2019-2021 Adrien Delle Cave
# SPDX-License-Identifier: GPL-3.0-or-later
"""
monit-docker
"""

from __future__ import absolute_import

__version__ = '0.0.38'
__version__ = '0.0.41'

import argparse
import codecs
Expand Down Expand Up @@ -39,6 +39,8 @@ import bitmath

from mako.template import Template

from sonicprobe import helpers

import yaml

try:
Expand All @@ -47,73 +49,77 @@ except ImportError:
from yaml import SafeLoader as YamlLoader, SafeDumper as YamlDumper


SYSLOG_NAME = "monit-docker"
LOG = logging.getLogger(SYSLOG_NAME)

DEFAULT_CONFFILE = "/etc/monit-docker/monit-docker.yml"
DEFAULT_LOGFILE = "/var/log/monit-docker/monit-docker.log"

MONIT_DOCKER_CONFIG = os.environ.get('MONIT_DOCKER_CONFIG')
MONIT_DOCKER_CONFFILE = os.environ.get('MONIT_DOCKER_CONFFILE') or DEFAULT_CONFFILE
MONIT_DOCKER_LOGFILE = os.environ.get('MONIT_DOCKER_LOGFILE') or DEFAULT_LOGFILE

_SUBCMDS = {}
_TPL_IMPORTS = ('from os import environ as ENV',
'from sonicprobe.helpers import to_yaml as my')

DOCKER_COMMANDS = ('start',
'stop',
'remove',
'reload',
'restart',
'kill',
'pause',
'unpause')

RESOURCE_CHOICES = ('mem_usage',
'mem_limit',
'mem_percent',
'cpu_percent',
'io_read',
'io_write',
'net_tx',
'net_rx',
'status')

STATUS_RC = {'running': 0,
'created': 10,
'paused': 20,
'restarting': 30,
'removing': 40,
'exited': 50,
'dead': 60}

DATATYPES = RESOURCE_CHOICES
DATATYPES_BEFORE_RUN = ('status',)

PRE_COND_RE = (r'(?:\s*(?P<pre_value>[0-9]+(?:\.[0-9]+)?\s*(?P<pre_value_unit>[a-zA-Z]+)?)\s+' +
r'(?P<pre_op>[\!\<\>=]=|[\<\>])\s+)?\s*')
DATATYPE_RE = r'(?P<datatype>[a-z_]+)\s*'
OP_RE = r'(?P<op>[\!\<\>=]=|[\<\>]|\s+in\s+|\s+not in\s+)\s*'
VALUE_RE = r'(?P<value>(?:[0-9]+(?:\.[0-9]+)?\s*(?P<value_unit>[a-zA-Z]+)?|[a-z]+|\((?:[a-z]+\,?){1,64}\)))'
CMD_RE = r'(?P<cmd>[^@].{2,})'
CMD_ALIAS_RE = r'@(?P<cmd_alias>[a-zA-Z][a-zA-Z0-9_\-\.]{0,64})'
COND_ALIAS_RE = r'@(?P<cond_alias>[a-zA-Z][a-zA-Z0-9_\-\.]{0,64})'

COND_MATCH = re.compile(r'^' + PRE_COND_RE + DATATYPE_RE + OP_RE + VALUE_RE + r'\s*$').match

CMD_MATCH = re.compile(r'^\s*' + CMD_RE + r'\s*$').match
CTN_GRP_MATCH = re.compile(r'^(?P<subset>id|image|name|label)\s*:\s*(?P<pattern>.+)\s*$').match

EXPR_MATCH = re.compile(r'^(?:(?:(?P<cond>' + PRE_COND_RE + DATATYPE_RE + OP_RE + VALUE_RE + r')\s*|' +
r'\s*' + COND_ALIAS_RE + r')\s*' +
r'\?)?\s*(?:' + CMD_ALIAS_RE + r'|' + CMD_RE + r')\s*$').match


StatusLoopContinue = object()
StatusLoopRunContinue = object()
StatusLoopBreak = object()
StatusFuncReturn = object()
SYSLOG_NAME = "monit-docker"
LOG = logging.getLogger(SYSLOG_NAME)

DEFAULT_CONFFILE = "/etc/monit-docker/monit-docker.yml"
DEFAULT_LOGFILE = "/var/log/monit-docker/monit-docker.log"
DEFAULT_RUNTIMEDIR = "/run/monit-docker"

MONIT_DOCKER_CONFIG = os.environ.get('MONIT_DOCKER_CONFIG')
MONIT_DOCKER_CONFFILE = os.environ.get('MONIT_DOCKER_CONFFILE') or DEFAULT_CONFFILE
MONIT_DOCKER_LOGFILE = os.environ.get('MONIT_DOCKER_LOGFILE') or DEFAULT_LOGFILE
MONIT_DOCKER_RUNTIMEDIR = os.environ.get('MONIT_DOCKER_RUNTIMEDIR') or DEFAULT_RUNTIMEDIR

_SUBCMDS = {}
_TPL_IMPORTS = ('from os import environ as ENV',
'from sonicprobe.helpers import to_yaml as my')

DOCKER_COMMANDS = ('start',
'stop',
'remove',
'reload',
'restart',
'kill',
'pause',
'unpause')

RESOURCE_CHOICES = ('mem_usage',
'mem_limit',
'mem_percent',
'cpu_percent',
'io_read',
'io_write',
'net_tx',
'net_rx',
'status',
'pid')

STATUS_RC = {'running': 0,
'created': 10,
'paused': 20,
'restarting': 30,
'removing': 40,
'exited': 50,
'dead': 60}

DATATYPES = RESOURCE_CHOICES
DATATYPES_BEFORE_RUN = ('pid',
'status',)

PRE_COND_RE = (r'(?:\s*(?P<pre_value>[0-9]+(?:\.[0-9]+)?\s*(?P<pre_value_unit>[a-zA-Z]+)?)\s+' +
r'(?P<pre_op>[\!\<\>=]=|[\<\>])\s+)?\s*')
DATATYPE_RE = r'(?P<datatype>[a-z_]+)\s*'
OP_RE = r'(?P<op>[\!\<\>=]=|[\<\>]|\s+in\s+|\s+not in\s+)\s*'
VALUE_RE = r'(?P<value>(?:[0-9]+(?:\.[0-9]+)?\s*(?P<value_unit>[a-zA-Z]+)?|[a-z]+|\((?:[a-z]+\,?){1,64}\)))'
CMD_RE = r'(?P<cmd>[^@].{2,})'
CMD_ALIAS_RE = r'@(?P<cmd_alias>[a-zA-Z][a-zA-Z0-9_\-\.]{0,64})'
COND_ALIAS_RE = r'@(?P<cond_alias>[a-zA-Z][a-zA-Z0-9_\-\.]{0,64})'

COND_MATCH = re.compile(r'^' + PRE_COND_RE + DATATYPE_RE + OP_RE + VALUE_RE + r'\s*$').match

CMD_MATCH = re.compile(r'^\s*' + CMD_RE + r'\s*$').match
CTN_GRP_MATCH = re.compile(r'^(?P<subset>id|image|name|label)\s*:\s*(?P<pattern>.+)\s*$').match

EXPR_MATCH = re.compile(r'^(?:(?:(?P<cond>' + PRE_COND_RE + DATATYPE_RE + OP_RE + VALUE_RE + r')\s*|' +
r'\s*' + COND_ALIAS_RE + r')\s*' +
r'\?)?\s*(?:' + CMD_ALIAS_RE + r'|' + CMD_RE + r')\s*$').match


StatusLoopContinue = object()
StatusLoopRunContinue = object()
StatusLoopBreak = object()
StatusFuncReturn = object()

try:
codecs.lookup_error('surrogateescape')
Expand Down Expand Up @@ -180,6 +186,10 @@ def argv_parse_check():
dest = 'logfile',
default = MONIT_DOCKER_LOGFILE,
help = "Use log file <logfile> instead of %(default)s")
parser.add_argument("--runtimedir",
dest = 'runtimedir',
default = MONIT_DOCKER_RUNTIMEDIR,
help = "Use runtime directory <runtimedir> instead of %(default)s")
parser.add_argument("--name",
action = 'append',
dest = 'name',
Expand Down Expand Up @@ -274,6 +284,14 @@ class MonitDockerSubCmdAbstract(object): #pylint: disable=useless-object-inherit

return STATUS_RC[status]

def _write_pidfile(self, pid, container_name):
if not self.options.runtimedir:
LOG.warning("runtime directory not configured or doesn't exist")
return

helpers.file_w_tmp((str(pid) + '\n',),
os.path.join(self.options.runtimedir, "%s.pid" % container_name))

def _reset_subsets(self):
self._subsets = {'id': [],
'image': [],
Expand Down Expand Up @@ -748,7 +766,9 @@ class MonitDockerSubCmdStats(MonitDockerSubCmdAbstract):
r = ["%s" % container['name']]

for rsc in self.options.resource:
if rsc == 'status':
if rsc == 'pid':
r.append("%s:%s" % (rsc, container['obj'].attrs['State'].get('Pid') or 'null'))
elif rsc == 'status':
r.append("%s:%s" % (rsc, container['obj'].status))
elif data:
r.append("%s:%s" % (rsc, self._get_resource_info(rsc, data, container['stats'])))
Expand All @@ -761,7 +781,9 @@ class MonitDockerSubCmdStats(MonitDockerSubCmdAbstract):
r = {container['name']: {}}

for rsc in self.options.resource:
if rsc == 'status':
if rsc == 'pid':
r[container['name']][rsc] = container['obj'].attrs['State'].get('Pid')
elif rsc == 'status':
r[container['name']][rsc] = container['obj'].status
elif data:
r[container['name']][rsc] = self._get_resource_info(rsc, data, container['stats'])
Expand Down Expand Up @@ -814,7 +836,7 @@ class MonitDockerSubCmdStats(MonitDockerSubCmdAbstract):
if not container['stats']:
container['stats'] = data
continue
elif data['read'] == container['stats']['read']:
if data['read'] == container['stats']['read']:
continue

r = self.run(container, data)
Expand Down Expand Up @@ -1020,6 +1042,9 @@ class MonitDockerSubCmdMonit(MonitDockerSubCmdStats):
return rs

def _get_datatype_info(self, datatype, container, data):
if datatype == 'pid':
return container['obj'].attrs['State'].get('Pid') or ''

if datatype == 'status':
return container['obj'].status

Expand Down Expand Up @@ -1115,8 +1140,13 @@ class MonitDockerSubCmdMonit(MonitDockerSubCmdStats):

def before_run(self, container):
if self.options.resource:
if len(self.options.resource) == 1 and self.options.resource[0] == 'status':
raise MonitDockerExit(int(self._get_status_rc(container['obj'].status)))
if len(self.options.resource) == 1:
if self.options.resource[0] == 'pid':
self._write_pidfile(container['obj'].attrs['State'].get('Pid') or '',
container['name'])
return StatusLoopRunContinue
if self.options.resource[0] == 'status':
raise MonitDockerExit(int(self._get_status_rc(container['obj'].status)))

if container['obj'].status not in ('paused', 'running'):
return StatusLoopRunContinue
Expand Down Expand Up @@ -1192,6 +1222,13 @@ def main(options):
root_logger = logging.getLogger('')
root_logger.addHandler(filehandler)

if options.runtimedir and not os.path.isdir(options.runtimedir):
try:
helpers.make_dirs(options.runtimedir)
except Exception:
LOG.warning("unable to create runtime directory: %r", options.runtimedir)
setattr(options, 'runtimedir', None)

rc = 0
monit_docker = None

Expand Down
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ Package: monit-docker
Architecture: all
Homepage: https://www.commandersact.com
Depends: ${misc:Depends}, python (>= 2.7~)
Pre-Depends: lsb-release, python-dev (>= 2.7~), python-pip, python-setuptools
Pre-Depends: lsb-release, python-dev (>= 2.7~), python-backports.ssl-match-hostname, python-pip, python-setuptools
Description: Fjord Technologies monit-docker package
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ services:
MONIT_DOCKER_CRONS: |
*/2 * * * * monit-docker monit --cmd-if 'mem_usage > 200 MiB ? (kill -USR2 1)'
*/2 * * * * monit-docker monit --cmd-if 'status not in (pause,running) ? restart'
*/2 * * * * monit-docker monit --rsc pid
volumes:
- /var/run:/var/run:rw
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ Mako
pyOpenSSL>=20.0.0
PyYAML>=3.10
six>=1.13.0
sonicprobe>=0.3.29
sonicprobe>=0.3.40
6 changes: 3 additions & 3 deletions setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: monit-docker
description: monit-docker
author: Adrien Delle Cave
author_email: pypi@doowan.net
copyright: '2020 Adrien Delle Cave'
release: '0.0.38'
version: '0.0.38'
copyright: '2021 Adrien Delle Cave'
release: '0.0.41'
version: '0.0.41'
license: License GPL-3
url: https://github.com/decryptus/monit-docker
python_requires:
Expand Down

0 comments on commit a696be7

Please sign in to comment.