Support health check for Docker containers

The docker has supported healthcheck. It is useful for application
container.
Zun may integrate these options.

  --health-cmd Command to run to check health
  --health-interval Time between running the check
  --health-retries Consecutive failures needed to report unhealthy
  --health-timeout Maximum time to allow one check to run
  In zun, the four parameter are cmd, interval, retries, timeout.

Partial-Implements: blueprint support-healthycheck

Change-Id: I2f9f48aef17f0d92835db13c6112550bda5e31bf
This commit is contained in:
weikeyou
2018-08-01 10:06:43 +08:00
parent dd35349899
commit c18105dea5
7 changed files with 116 additions and 6 deletions

View File

@@ -31,7 +31,7 @@ if not LOG.handlers:
HEADER_NAME = "OpenStack-API-Version"
SERVICE_TYPE = "container"
MIN_API_VERSION = '1.1'
MAX_API_VERSION = '1.21'
MAX_API_VERSION = '1.22'
DEFAULT_API_VERSION = MAX_API_VERSION
_SUBSTITUTIONS = {}

View File

@@ -16,6 +16,7 @@
import json
import os
import re
from oslo_utils import netutils
from six.moves.urllib import parse
@@ -276,6 +277,52 @@ def parse_nets(ns):
return nets
def parse_health(hc_str):
err_msg = ("Invalid healthcheck argument '%s'. healthcheck arguments"
" must be of the form --healthcheck <cmd='command',"
"interval=time,retries=integer,timeout=time>, and the unit "
"of time is s(seconds), m(minutes), h(hours).") % hc_str
keys = ["cmd", "interval", "retries", "timeout"]
health_info = {}
for kv_str in hc_str[0].split(","):
try:
k, v = kv_str.split("=", 1)
k = k.strip()
v = v.strip()
except ValueError:
raise apiexec.CommandError(err_msg)
if k in keys:
if health_info.get(k):
raise apiexec.CommandError(err_msg)
elif k in ['interval', 'timeout']:
health_info[k] = _convert_healthcheck_para(v, err_msg)
elif k == "retries":
health_info[k] = int(v)
else:
health_info[k] = v
else:
raise apiexec.CommandError(err_msg)
return health_info
def _convert_healthcheck_para(time, err_msg):
int_pattern = '^\d+$'
time_pattern = '^\d+(s|m|h)$'
ret = 0
if re.match(int_pattern, time):
ret = int(time)
elif re.match(time_pattern, time):
if time.endswith('s'):
ret = int(time.split('s')[0])
elif time.endswith('m'):
ret = int(time.split('m')[0]) * 60
elif time.endswith('h'):
ret = int(time.split('h')[0]) * 3600
else:
raise apiexec.CommandError(err_msg)
return ret
def normalise_file_path_to_url(path):
if parse.urlparse(path).scheme:
return path

View File

@@ -185,6 +185,19 @@ class CreateContainer(command.ShowOne):
action='store_true',
default=False,
help='Give extended privileges to this container')
parser.add_argument(
'--healthcheck',
action='append',
default=[],
metavar='<cmd=test_cmd,interval=time,retries=n,timeout=time>',
help='Specify a test cmd to perform to check that the container'
'is healthy. '
'cmd: Command to run to check health. '
'interval: Time between running the check (|s|m|h)'
' (default 0s). '
'retries: Consecutive failures needed to report unhealthy.'
'timeout: Maximum time to allow one check to run (s|m|h)'
' (default 0s).')
return parser
def take_action(self, parsed_args):
@@ -218,6 +231,9 @@ class CreateContainer(command.ShowOne):
opts['disk'] = parsed_args.disk
opts['availability_zone'] = parsed_args.availability_zone
opts['auto_heal'] = parsed_args.auto_heal
if parsed_args.healthcheck:
opts['healthcheck'] = \
zun_utils.parse_health(parsed_args.healthcheck)
opts = zun_utils.remove_null_parms(**opts)
container = client.containers.create(**opts)
@@ -811,6 +827,19 @@ class RunContainer(command.ShowOne):
action='store_true',
default=False,
help='Give extended privileges to this container')
parser.add_argument(
'--healthcheck',
action='append',
default=[],
metavar='<cmd=test_cmd,interval=time,retries=n,timeout=time>',
help='Specify a test cmd to perform to check that the container'
'is healthy. '
'cmd: Command to run to check health. '
'interval: Time between running the check (s|m|h)'
' (default 0s). '
'retries: Consecutive failures needed to report unhealthy.'
'timeout: Maximum time to allow one check to run (s|m|h)'
' (default 0s).')
return parser
def take_action(self, parsed_args):
@@ -844,6 +873,9 @@ class RunContainer(command.ShowOne):
opts['disk'] = parsed_args.disk
opts['availability_zone'] = parsed_args.availability_zone
opts['auto_heal'] = parsed_args.auto_heal
if parsed_args.healthcheck:
opts['healthcheck'] = \
zun_utils.parse_health(parsed_args.healthcheck)
opts = zun_utils.remove_null_parms(**opts)
container = client.containers.run(**opts)

View File

@@ -246,7 +246,7 @@ class ShellTest(utils.TestCase):
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='', profile=None,
endpoint_override=None, insecure=False, cacert=None,
version=api_versions.APIVersion('1.21'))
version=api_versions.APIVersion('1.22'))
def test_main_option_region(self):
self.make_env()
@@ -274,7 +274,7 @@ class ShellTest(utils.TestCase):
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='', profile=None,
endpoint_override=None, insecure=False, cacert=None,
version=api_versions.APIVersion('1.21'))
version=api_versions.APIVersion('1.22'))
@mock.patch('zunclient.client.Client')
def test_main_endpoint_internal(self, mock_client):
@@ -288,7 +288,7 @@ class ShellTest(utils.TestCase):
project_domain_id='', project_domain_name='',
user_domain_id='', user_domain_name='', profile=None,
endpoint_override=None, insecure=False, cacert=None,
version=api_versions.APIVersion('1.21'))
version=api_versions.APIVersion('1.22'))
class ShellTestKeystoneV3(ShellTest):
@@ -320,4 +320,4 @@ class ShellTestKeystoneV3(ShellTest):
user_domain_id='', user_domain_name='Default',
endpoint_override=None, insecure=False, profile=None,
cacert=None,
version=api_versions.APIVersion('1.21'))
version=api_versions.APIVersion('1.22'))

View File

@@ -38,6 +38,7 @@ CONTAINER1 = {'id': '1234',
'disk': '20',
'auto_heal': False,
'privileged': False,
'healthcheck': {}
}
CONTAINER2 = {'id': '1235',
@@ -59,6 +60,7 @@ CONTAINER2 = {'id': '1235',
'hostname': 'testhost',
'auto_heal': False,
'privileged': True,
'healthcheck': {}
}
NETWORK1 = {'net_id': '99e90853-e1fd-4c57-a116-9e335deaa592',

View File

@@ -25,7 +25,8 @@ CREATION_ATTRIBUTES = ['name', 'image', 'command', 'cpu', 'memory',
'restart_policy', 'interactive', 'image_driver',
'security_groups', 'hints', 'nets', 'auto_remove',
'runtime', 'hostname', 'mounts', 'disk',
'availability_zone', 'auto_heal', 'privileged']
'availability_zone', 'auto_heal', 'privileged',
'healthcheck']
class Container(base.Resource):

View File

@@ -153,6 +153,18 @@ def _show_container(container):
action='store_true',
default=False,
help='Give extended privileges to this container')
@utils.arg('--healthcheck',
action='append',
default=[],
metavar='<cmd=command,interval=time,retries=integer,timeout=time>',
help='Specify a test cmd to perform to check that the container'
'is healthy. '
'cmd: Command to run to check health. '
'interval: Time between running the check (s|m|h)'
' (default 0s). '
'retries: Consecutive failures needed to report unhealthy. '
'timeout: Maximum time to allow one check to run (s|m|h)'
' (default 0s).')
def do_create(cs, args):
"""Create a container."""
opts = {}
@@ -175,6 +187,8 @@ def do_create(cs, args):
opts['availability_zone'] = args.availability_zone
opts['auto_heal'] = args.auto_heal
opts['command'] = args.command
if args.healthcheck:
opts['healthcheck'] = zun_utils.parse_health(args.healthcheck)
if args.security_group:
opts['security_groups'] = args.security_group
@@ -647,6 +661,18 @@ def do_kill(cs, args):
action='store_true',
default=False,
help='Give extended privileges to this container')
@utils.arg('--healthcheck',
action='append',
default=[],
metavar='<cmd=command,interval=time,retries=integer,timeout=time>',
help='Specify a test cmd to perform to check that the container'
'is healthy. '
'cmd: Command to run to check health. '
'interval: Time between running the check (s|m|h)'
' (default 0s). '
'retries: Consecutive failures needed to report unhealthy. '
'timeout: Maximum time to allow one check to run (s|m|h)'
' (default 0s).')
def do_run(cs, args):
"""Run a command in a new container."""
opts = {}
@@ -669,6 +695,8 @@ def do_run(cs, args):
opts['availability_zone'] = args.availability_zone
opts['auto_heal'] = args.auto_heal
opts['command'] = args.command
if args.healthcheck:
opts['healthcheck'] = zun_utils.parse_health(args.healthcheck)
if args.security_group:
opts['security_groups'] = args.security_group