Add in-instance docker software config hook

This hook uses 'docker-py' library to deploy containers
for a container-manifest embeded in software-config 'config'
property.

Container manifest uses standard google container manifest schema[1].

WARNING: This is an experimental implementation.

[1]https://cloud.google.com/compute/docs/containers/container_vms.

Change-Id: I5ca30e99be1a247daf89c46aaa5a5034f1991379
Closes-Bug: #1380886
This commit is contained in:
Rabi Mishra 2014-10-13 20:08:03 +05:30 committed by Thomas Herve
parent e8f83e0882
commit 40d2b4f6b8
4 changed files with 298 additions and 0 deletions

View File

@ -35,6 +35,7 @@ with the following:
os-apply-config \
heat-config-ansible \
heat-config-cfn-init \
heat-config-docker \
heat-config-puppet \
heat-config-salt \
heat-config-script \

View File

@ -0,0 +1,43 @@
This hook uses 'docker-py' library to deploy containers for a container-manifest(POD)
embeded inside software-config 'config' property.
Container manifest uses standard google container manifest schema[1].
WARNING: This is an experimental implementation.
Specific Inputs and outputs are ignored.
All container ports are bound to specific host ports specified in the manifest.
if a 'hostPort' is not specified, 'containerPort' is used as the 'hostPort'.
This may result in port conflict. IP-PER-POD model is a possible enhancement.
Container manifest:
version: v1beta2 // Required.
containers: // Required.
- name: string // Required.
image: string // Required.
command: [string]
workingDir: string
volumeMounts:
- name: string
mountPath: string
readOnly: boolean
ports:
- name: string
containerPort: int
hostPort: int
protocol: string
env:
- name: string
value: string
restartPolicy:
- string: {}
volumes:
- name: string
source: emptyDir | HostDir
emptyDir: {}
hostDir:
path: string
[1]https://cloud.google.com/compute/docs/containers/container_vms.

View File

@ -0,0 +1,17 @@
#!/bin/bash
set -x
SCRIPTDIR=$(dirname $0)
if [ -f /etc/debian_version ]; then
install-packages docker.io
update-rc.d docker.io defaults
elif [ -f /etc/redhat-release ]; then
install-packages docker-io
systemctl enable docker.service
fi
pip install docker-py
install -D -g root -o root -m 0755 ${SCRIPTDIR}/hook-docker.py /var/lib/heat-config/hooks/docker

View File

@ -0,0 +1,237 @@
#!/usr/bin/env python
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
import os
import sys
import docker
import yaml
DOCKER_BASE_URL = os.environ.get('DOCKER_HOST',
'unix://var/run/docker.sock')
def is_running(client, container_id):
container_info = get_container_info(client, container_id)
if container_info:
return container_info.get('State', {}).get('Running')
return False
def remove_container(client, container_id):
if is_running(client, container_id):
client.kill(container_id)
client.remove_container(container_id, False)
def build_container_name(pod_name, container_name):
return pod_name + '.' + container_name
def remove_all_containters_for_pod(client, pod_name):
containers = client.containers(all=True)
for x in containers:
if pod_name in x['Names'][0]:
remove_container(client, x['Id'])
def get_container_info(client, container):
return client.inspect_container(container)
def pull_image(client, image):
try:
client.inspect_image(image)
except Exception:
client.pull(image, tag=None)
def get_client():
kwargs = {}
kwargs['base_url'] = DOCKER_BASE_URL
client = docker.Client(**kwargs)
client._version = client.version()['ApiVersion']
return client
def volume_exists(volumes, volume_mount):
for volume in volumes:
if volume_mount['name'] == volume['name']:
return True
return False
def make_binds(volume_mounts, volumes):
binds = {}
mountpoints = {}
if isinstance(volume_mounts, list):
for volume_mount in volume_mounts:
if volume_exists(volumes, volume_mount):
b = {}
if volume_mount['mountPath']:
b['bind'] = volume_mount['mountPath']
else:
b['bind'] = volume_mount['name']
mountpoints[b['bind']] = {}
if volume_mount['readOnly']:
b['ro'] = True
binds[get_path('/', volume_mount['name'])] = b
return mountpoints, binds
def make_env_vars(env):
env_vars = {}
for e in env:
env_vars[e['name']] = e['value']
return env_vars
def get_path(root_dir, vol_name):
return root_dir + vol_name
def mount_external_volumes(volumes):
root_dir = '/'
for vol in volumes:
vol_path = get_path(root_dir, vol['name'])
if not os.path.exists(vol_path):
os.makedirs(vol_path)
def make_port_bindings(client, container_id, ports):
if not is_running(client, container_id):
bindings = {}
if isinstance(ports, list):
for port in ports:
if port['hostPort']:
exposed_port = port['hostPort']
else:
exposed_port = port['containerPort']
bindings[port['containerPort']] = exposed_port
return bindings
def update_pod_state(pod_state, is_container_running):
if pod_state != 1 and is_container_running:
return 0 # pod without error
else:
return 1 # pod with error
def main(argv=sys.argv):
log = logging.getLogger('heat-config')
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(
logging.Formatter(
'[%(asctime)s] (%(name)s) [%(levelname)s] %(message)s'))
log.addHandler(handler)
log.setLevel('DEBUG')
c = json.load(sys.stdin)
client = get_client()
pod_name = c.get('name')
config = c.get('config')
log.debug('Received Config %s' % config)
if isinstance(config, dict):
yaml_config = config
else:
yaml_config = yaml.load(config)
containers = yaml_config.get('containers')
volumes = yaml_config.get('volumes')
remove_all_containters_for_pod(client, pod_name)
mount_external_volumes(volumes)
stdout, stderr = {}, {}
pod_state = -1
for container in containers:
image = container.get('image')
name = container.get('name')
command = container.get('command')
working_dir = container.get('workingDir')
volume_mounts = container.get('volumeMounts')
ports = container.get('ports')
env_vars = make_env_vars(container.get('env'))
container_id = None
log.debug('Pulling docker image %s.' % image)
try:
log.debug('Pulling docker image %s.' % image)
pull_image(client, image)
log.debug('Making volume bindings.')
mountpoints, binds = make_binds(volume_mounts, volumes)
container_info = client.create_container(
image=image,
command=command,
working_dir=working_dir,
ports=ports,
environment=env_vars,
volumes=mountpoints,
name=build_container_name(pod_name, name)
)
log.debug('Container created for image: %s, container info: %s.'
% (image, container_info))
container_id = container_info['Id']
log.debug('Building volume mounts.')
bindings = make_port_bindings(client, container_id, ports)
if not is_running(client, container_id):
client.start(container_id,
binds=binds,
port_bindings=bindings)
log.debug('Started Container %s.' % container_id)
stdout[container_id] = get_container_info(client, container_id)
pod_state = update_pod_state(pod_state, True)
except Exception as ex:
log.error('An error occurred deploying container %s with error %s.'
% (image, ex))
if container_id:
stderr[container_id] = ex
else:
stderr[image] = ex
pod_state = update_pod_state(pod_state, False)
remove_all_containters_for_pod(pod_name)
response = {}
response.update({
'deploy_stdout': stdout,
'deploy_stderr': stderr,
'deploy_status_code': pod_state,
})
json.dump(response, sys.stdout)
if __name__ == '__main__':
sys.exit(main(sys.argv))