Skip to content

Instantly share code, notes, and snippets.

@furqanrydhan
Last active February 20, 2019 23:28
Show Gist options
  • Save furqanrydhan/db2291d102d31aee1fe42e5d39a27dac to your computer and use it in GitHub Desktop.
Save furqanrydhan/db2291d102d31aee1fe42e5d39a27dac to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import socket
from multiprocessing import Process
import json
import logging
import logging.handlers
import subprocess
import argparse
import random
from time import sleep
import requests
parser = argparse.ArgumentParser(description="Deploy Docker")
parser.add_argument("--cluster-name", dest="cluster_name", help="cluster to operate as")
parser.add_argument("--service-names", dest="service_names", help="service names to deploy", nargs="*")
logging.basicConfig(level=logging.DEBUG, format='docker_deploy %(levelname)s [%(threadName)s:%(module)s.py:%(lineno)d] %(message)s')
logger = logging.getLogger('docker_deploy')
INTERVAL = random.randint(10, 30)
SERVICES = []
'''
Service
{
"name": "company/some-project", # docker repo
"version": "0.0.1",
"volumes": ['/tmp:/tmp'],
"env": []
}
'''
def get_running_status_for_service(service_name):
cmd = ['/usr/bin/docker', 'inspect', service_name]
try:
proc = call(cmd)
stdout = proc.stdout.decode('utf-8').strip()
inspected = json.loads(stdout)[0] #inspect returns array of 1 object
running_version = inspected['Config']['Image'].split(":")[1]
return inspected['State']['Running'], running_version
except (subprocess.CalledProcessError, KeyError):
return None, None
def get_image_id_for_image(image):
cmd = ['/usr/bin/docker', 'image', 'inspect', image, '--format="{{json .Id}}"']
try:
proc = call(cmd)
except subprocess.CalledProcessError:
return None
stdout = proc.stdout.decode('utf-8')
image_id = stdout.split(":")[1].split('"')[0]
return image_id
def call(command):
# logger.info('Calling {}'.format(command))
return subprocess.run(command, stdout=subprocess.PIPE, check=True)
def download_file(url, destination):
r = requests.get(url, stream=True)
try:
r.raise_for_status()
with open(destination, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
except requests.HTTPError as e:
raise e
class NotFoundException(Exception):
pass
def cleanup_directory():
docker_rmi_cmd = ['/usr/bin/docker', 'system', 'prune', '-af']
call(docker_rmi_cmd)
def docker_list_running_containers():
docker_ps_cmd = ['/usr/bin/docker', 'container', 'ls', '--format', "{{ .Names }}"]
try:
proc = call(docker_ps_cmd)
stdout = proc.stdout.decode('utf-8').strip()
return stdout.split('\n')
except subprocess.CalledProcessError:
return []
def docker_check_image(service):
service_name = service['name']
version = service['version']
service_running, running_version = get_running_status_for_service(service_name)
if service_running and running_version == version:
# logger.info('Not running remove for {} as the running_version matches version {}'.format(service_name, running_version))
return False
return True
def docker_pull(service):
"""
Ensure the proper version of the service is loaded into docker
"""
service_name = service['name']
version = service['version']
image = service_name + ':' + version
logger.info("Pulling {} ({}) from Docker registry".format(service_name, image))
pull_cmd = ['/usr/bin/docker', 'pull', image]
call(pull_cmd)
def docker_run(service):
"""
Ensure the service is running, with the proper network, volumes, etc.
"""
service_name = service['name']
version = service['version']
image = service_name + ':' + version
try:
remove_cmd = ['/usr/bin/docker', 'rm', '-f', service_name]
call(remove_cmd)
except subprocess.CalledProcessError:
pass
fqdn = socket.getfqdn()
prefix = fqdn.split(".")[0]
container_hostname = "{}.{}".format(prefix, service_name)
run_cmd = [
'/usr/bin/docker', 'run', '-d',
'--network', 'host',
'--hostname', container_hostname,
'--name', service_name
]
for volume in service['volumes']:
run_cmd.extend(['-v', volume])
run_cmd.append(image)
logger.info('Running up {} with {}'.format(service_name, run_cmd))
call(run_cmd)
def pull_and_run(service):
docker_pull(service)
docker_run(service)
def deploy_services():
# logger.info('service_names: {}'.format(service_names))
deploy_procs = []
for service in SERVICES:
try:
do_deploy = docker_check_image(service)
if do_deploy:
logger.info('Deploying {}'.format(service_name))
proc = Process(target=pull_and_run, args=(service,))
proc.start()
deploy_procs.append(proc)
else:
logger.info('{} Does not exist so not deploying'.format(service_name))
except NotFoundException:
logger.warn('ETCD deploy value for {} not found'.format(service_name))
except Exception:
logger.exception("couldn't deploy: {}".format(service_name))
for proc in deploy_procs:
proc.join()
cleanup_directory()
def main():
run = True
while run:
try:
deploy_services()
except KeyboardInterrupt:
run = False
except Exception:
logger.exception('Uncaught exception in main run loop')
sleep(INTERVAL)
if __name__ == '__main__':
try:
main()
except Exception as e:
logger.exception(e)
[Unit]
Description=Docker deploys
After=network.target
[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/docker_deploy.py
SyslogIdentifier=docker_deploy
RestartSec=1s
Restart=always
[Install]
WantedBy=multi-user.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment