Boot config to deploy containers

This defines environment files which declare a resource type
Heat::InstallConfigAgent.

This can be used by server user_data when booting a pristine
image to install parts to deploy a container with docker-compose
or atomic hook.

This container would then help deploy all other 'docker-compose'
and 'atomic' SoftwareConfig resources.

Example template can be run with downloadable Fedora Atomic Host
image or RHEL Atomic Host, specifically tailored for use
with containers.

https://getfedora.org/cloud/download

Change-Id: Ib5bdcef1111ec9a0bcf588ece277b9328f6e6def
This commit is contained in:
Rabi Mishra 2015-03-15 19:58:52 +05:30
parent eb3b82b7d1
commit 24e45841cb
16 changed files with 492 additions and 0 deletions

View File

@ -30,3 +30,26 @@ When creating the stack, reference the desired environment, eg:
heat stack-create -e fedora_yum_env.yaml \
-f ../example-templates/example-config-pristine-image.yaml \
deploy-to-pristine
=====================================
Boot config with heat-container-agent
=====================================
When creating the stack to deploy containers with docker-compose,
include the following in the template:
boot_config:
type: Heat::InstallConfigAgent
server:
type: OS::Nova::Server
properties:
user_data_format: SOFTWARE_CONFIG
user_data: {get_attr: [boot_config, config]}
# ...
and refrence the desired environment, eg:
heat stack-create -e container_agent_env.yaml \
-f ../example-templates/example-pristine-atomic-docker-compose.yaml \
deploy-to-pristine

View File

@ -0,0 +1,4 @@
# Installs docker software-config agents on boot in a container
resource_registry:
"Heat::InstallConfigAgent": templates/install_container_agent.yaml

View File

@ -0,0 +1,18 @@
#cloud-config
merge_how: dict(recurse_array)+list(append)
write_files:
- path: /opt/container_agent/get_container_agent_image.sh
owner: "root:root"
permissions: "0644"
content: |
#!/bin/bash
set -eux
regex='(https?|http)://[-A-Za-z0-9\+&@#/%?=~_|!:,.;]*[-A-Za-z0-9\+&@#/%=~_|]'
agent_image="$1"
if [[ $agent_image =~ $regex ]]
then
cd /tmp && { curl $agent_image > heat_container_image.tar ; cd -; }
/usr/bin/docker load -i /tmp/heat_container_image.tar
else
/usr/bin/docker pull $agent_image
fi

View File

@ -0,0 +1,34 @@
#!/bin/bash
set -eux
# heat-docker-agent service
cat <<EOF > /etc/systemd/system/heat-container-agent.service
[Unit]
Description=Heat Container Agent
After=docker.service
Requires=docker.service
[Service]
User=root
Restart=on-failure
ExecStartPre=-/usr/bin/docker kill heat-container-agent
ExecStartPre=-/usr/bin/docker rm heat-container-agent
ExecStartPre=/opt/agent_container/get_container_agent_image.sh $agent_image
ExecStart=/usr/bin/docker run --name heat-container-agent --privileged --net=host -v /usr/bin/atomic:/usr/bin/atomic -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/atomic:/usr/bin/atomic -v /var/lib/cloud:/var/lib/cloud -v /var/lib/heat-cfntools:/var/lib/heat-cfntools $agent_image
ExecStop=/usr/bin/docker stop heat-container-agent
[Install]
WantedBy=multi-user.target
EOF
# enable and start docker
/usr/bin/systemctl enable docker.service
/usr/bin/systemctl start --no-block docker.service
# enable and start heat-container-agent
chmod 0640 /etc/systemd/system/heat-container-agent.service
chmod 0755 /opt/agent_container/get_agent_container_image.sh
/usr/bin/systemctl enable heat-container-agent.service
/usr/bin/systemctl start --no-block heat-container-agent.service

View File

@ -0,0 +1,33 @@
heat_template_version: 2014-10-16
parameters:
agent_image:
type: string
default: ramishra/heat-container-agent
resources:
write_image_pull_script:
type: "OS::Heat::SoftwareConfig"
properties:
group: ungrouped
config: {get_file: ./fragments/get_container_agent_image.frag}
install_container_agent:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
params:
$agent_image: {get_param: agent_image}
template: {get_file: ./fragments/start_container_agent.sh}
node_init:
type: "OS::Heat::MultipartMime"
properties:
parts:
- config: {get_resource: write_image_pull_script}
- config: {get_resource: install_container_agent}
outputs:
config:
value: {get_resource: node_init}

View File

@ -0,0 +1,98 @@
heat_template_version: 2014-10-16
description: >
A template which demonstrates doing boot-time deployment of docker
container with docker-compose agent.
This template expects to be created with an environment which defines
the resource type Heat::InstallConfigAgent such as
../boot-config-docker/heat_docker_agents_env.yaml
parameters:
key_name:
type: string
default: heat_key
flavor:
type: string
default: m1.small
image:
type: string
default: fedora-atomic
private_net:
type: string
default: private
public_net:
type: string
default: public
resources:
the_sg:
type: OS::Neutron::SecurityGroup
properties:
name: the_sg
description: Ping and SSH
rules:
- protocol: icmp
- protocol: tcp
port_range_min: 22
port_range_max: 22
- protocol: tcp
port_range_min: 5000
port_range_max: 5000
config:
type: OS::Heat::StructuredConfig
properties:
group: docker-compose
config:
web:
image: training/webapp
ports:
- 5000:5000
deployment:
type: OS::Heat::StructuredDeployment
properties:
name: test_deployment
config:
get_resource: config
server:
get_resource: server
boot_config:
type: Heat::InstallConfigAgent
server:
type: OS::Nova::Server
properties:
image: {get_param: image}
flavor: {get_param: flavor}
key_name: {get_param: key_name}
networks:
- network: {get_param: private_net}
security_groups:
- {get_resource: the_sg}
user_data_format: SOFTWARE_CONFIG
user_data: {get_attr: [boot_config, config]}
server_floating_ip_assoc:
type: OS::Neutron::FloatingIPAssociation
properties:
floatingip_id: {get_resource: floating_ip}
port_id: {get_attr: [server, addresses, {get_param: private_net}, 0, port]}
floating_ip:
type: OS::Neutron::FloatingIP
properties:
floating_network: {get_param: public_net}
outputs:
result:
value:
get_attr: [deployment, result]
stdout:
value:
get_attr: [deployment, deploy_stdout]
stderr:
value:
get_attr: [deployment, deploy_stderr]
status_code:
value:
get_attr: [deployment, deploy_status_code]

View File

@ -0,0 +1,6 @@
# Defines a Heat::InstallConfigAgent config resource which performs no config.
# This environment can be used when the image already has the required agents
# installed and configured.
#
resource_registry:
"Heat::InstallConfigAgent": "OS::Heat::SoftwareConfig"

View File

@ -0,0 +1,28 @@
FROM fedora
MAINTAINER “Rabi Mishra” <ramishra@redhat.com>
ENV container docker
ADD ./scripts/55-heat-config \
/opt/stack/os-config-refresh/configure.d/
ADD ./scripts/50-heat-config-docker-compose \
/opt/stack/os-config-refresh/configure.d/
ADD ./scripts/* \
/var/lib/heat-config/hooks/
ADD ./scripts/heat-config-notify \
/usr/bin/heat-config-notify
ADD ./scripts/configure_container_agent.sh /tmp/
RUN chmod 700 /tmp/configure_container_agent.sh ; \
/tmp/configure_container_agent.sh
#create volumes to share the host directories
VOLUME [ "/var/lib/cloud"]
VOLUME [ "/var/lib/heat-cfntools" ]
#set DOCKER_HOST environment variable that docker-compose would use
ENV DOCKER_HOST unix:///var/run/docker.sock
CMD /usr/bin/os-collect-config

View File

@ -0,0 +1,16 @@
=======================================================
Steps to build container image with all container hooks
=======================================================
Docker build does not work with soft links. Therefore, convert all
soft links to hardlinks.
$ find -type l -exec bash -c 'ln -f "$(readlink -m "$0")" "$0"' {} \;
Build docker image with container hooks.
$docker build -t xxxx/heat-container-agent ./
Push the image to docker hub.
$docker push xxxx/heat-container-agent

View File

@ -0,0 +1 @@
../../elements/heat-config-docker-compose/os-refresh-config/configure.d/50-heat-config-docker-compose

View File

@ -0,0 +1 @@
../../elements/heat-config/os-refresh-config/configure.d/55-heat-config

View File

@ -0,0 +1,116 @@
#!/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 ast
import json
import logging
import os
import subprocess
import sys
WORKING_DIR = os.environ.get('HEAT_ATOMIC_WORKING',
'/var/lib/heat-config/heat-config-atomic')
ATOMIC_CMD = os.environ.get('HEAT_ATOMIC_CMD', 'atomic')
def prepare_dir(path):
if not os.path.isdir(path):
os.makedirs(path, 0o700)
def build_response(deploy_stdout, deploy_stderr, deploy_status_code):
return {
'deploy_stdout': deploy_stdout,
'deploy_stderr': deploy_stderr,
'deploy_status_code': deploy_status_code,
}
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)
prepare_dir(WORKING_DIR)
os.chdir(WORKING_DIR)
env = os.environ.copy()
input_values = dict((i['name'], i['value']) for i in c['inputs'])
stdout, stderr = {}, {}
config = c.get('config', '')
if not config:
log.debug("No 'config' input found, nothing to do.")
json.dump(build_response(stdout, stderr, 0), sys.stdout)
return
atomic_subcmd = config.get('command', 'install')
image = config.get('image')
if input_values.get('deploy_action') == 'DELETE':
cmd = [
'uninstall',
atomic_subcmd,
image
]
subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env=env)
stdout, stderr = subproc.communicate()
json.dump(build_response(stdout, stderr, subproc.returncode), sys.stdout)
return
install_cmd = config.get('installcmd', '')
name = config.get('name', c.get('id'))
cmd = [
ATOMIC_CMD,
atomic_subcmd,
image,
'-n %s' % name
]
if atomic_subcmd == 'install':
cmd.extend([install_cmd])
privileged = config.get('privileged', False)
if atomic_subcmd == 'run' and privileged:
cmd.extend(['--spc'])
log.debug('Running %s' % cmd)
subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = subproc.communicate()
log.debug(stdout)
log.debug(stderr)
if subproc.returncode:
log.error("Error running %s. [%s]\n" % (cmd, subproc.returncode))
else:
log.debug('Completed %s' % cmd)
json.dump(build_response(stdout, stderr, subproc.returncode), sys.stdout)
if __name__ == '__main__':
sys.exit(main(sys.argv))

View File

@ -0,0 +1,111 @@
#!/bin/bash
set -eux
yum -y update
yum -y install os-collect-config os-apply-config \
os-refresh-config dib-utils python-pip python-docker-py
yum clean all
# pip installing dpath as python-dpath is an older version of dpath
pip install dpath
# using binary as 'docker-compose' and 'os-collect-config' has conflict on 'requests' version
# os-collect-config requires 2.5.3 where as for docker-compose it's <2.5.0
curl -L https://github.com/docker/compose/releases/download/1.1.0/docker-compose-`uname -s`-`uname -m` \
> /usr/local/bin/docker-compose
# os-apply-config templates directory
oac_templates=/usr/libexec/os-apply-config/templates
mkdir -p $oac_templates/etc
# initial /etc/os-collect-config.conf
cat <<EOF >/etc/os-collect-config.conf
[DEFAULT]
command = os-refresh-config
EOF
# template for building os-collect-config.conf for polling heat
cat <<EOF >$oac_templates/etc/os-collect-config.conf
[DEFAULT]
{{^os-collect-config.command}}
command = os-refresh-config
{{/os-collect-config.command}}
{{#os-collect-config}}
{{#command}}
command = {{command}}
{{/command}}
{{#polling_interval}}
polling_interval = {{polling_interval}}
{{/polling_interval}}
{{#cachedir}}
cachedir = {{cachedir}}
{{/cachedir}}
{{#collectors}}
collectors = {{collectors}}
{{/collectors}}
{{#cfn}}
[cfn]
{{#metadata_url}}
metadata_url = {{metadata_url}}
{{/metadata_url}}
stack_name = {{stack_name}}
secret_access_key = {{secret_access_key}}
access_key_id = {{access_key_id}}
path = {{path}}
{{/cfn}}
{{#heat}}
[heat]
auth_url = {{auth_url}}
user_id = {{user_id}}
password = {{password}}
project_id = {{project_id}}
stack_id = {{stack_id}}
resource_name = {{resource_name}}
{{/heat}}
{{#request}}
[request]
{{#metadata_url}}
metadata_url = {{metadata_url}}
{{/metadata_url}}
{{/request}}
{{/os-collect-config}}
EOF
mkdir -p $oac_templates/var/run/heat-config
# template for writing heat deployments data to a file
echo "{{deployments}}" > $oac_templates/var/run/heat-config/heat-config
# os-refresh-config scripts directory
# This moves to /usr/libexec/os-refresh-config in later releases
orc_scripts=/opt/stack/os-config-refresh
for d in pre-configure.d configure.d migration.d post-configure.d; do
install -m 0755 -o root -g root -d $orc_scripts/$d
done
# os-refresh-config script for running os-apply-config
cat <<EOF >$orc_scripts/configure.d/20-os-apply-config
#!/bin/bash
set -ue
exec os-apply-config
EOF
chmod 700 $orc_scripts/configure.d/20-os-apply-config
# create hooks directory
#hooks_dir=/var/lib/heat-config/hooks
#mkdir -p $hooks_dir
chmod 700 /opt/stack/os-config-refresh/configure.d/55-heat-config
chmod 700 /opt/stack/os-config-refresh/configure.d/50-heat-config-docker-compose
chmod 755 /var/lib/heat-config/hooks/atomic
chmod 755 /var/lib/heat-config/hooks/docker-compose
chmod 755 /var/lib/heat-config/hooks/script
chmod 755 /usr/bin/heat-config-notify

View File

@ -0,0 +1 @@
../../elements/heat-config-docker-compose/install.d/hook-docker-compose.py

View File

@ -0,0 +1 @@
../../elements/heat-config/bin/heat-config-notify

View File

@ -0,0 +1 @@
../../elements/heat-config-script/install.d/hook-script.py