Example #1
0
def recycle_daemon():
    """
    Render the docker template files and restart the docker daemon on this
    system.

    :return: None
    """
    charm_config = check_for_juju_https_proxy(config)
    validate_config(charm_config)

    hookenv.log('Restarting docker service.')

    # Re-render our docker daemon template at this time... because we're
    # restarting. And its nice to play nice with others. Isn't that nice?
    opts = DockerOpts()
    runtime = determine_apt_source()
    render(
        'docker.defaults', '/etc/default/docker', {
            'opts': opts.to_s(),
            'manual': config('docker-opts'),
            'docker_runtime': runtime
        })
    render('docker.systemd', '/lib/systemd/system/docker.service',
           charm_config)

    reload_system_daemons()
    host.service_restart('docker')

    if not _probe_runtime_availability():
        status_set('waiting', 'Container runtime not available.')
        return
Example #2
0
def install():
    ''' Install the docker daemon, and supporting tooling '''
    # Often when building layer-docker based subordinates, you dont need to
    # incur the overhead of installing docker. This tuneable layer option
    # allows you to disable the exec of that install routine, and instead short
    # circuit immediately to docker.available, so you can charm away!
    layer_opts = layer.options('docker')
    if layer_opts['skip-install']:
        set_state('docker.available')
        set_state('docker.ready')
        return

    status_set('maintenance', 'Installing AUFS and other tools')
    kernel_release = check_output(['uname', '-r']).rstrip()
    packages = [
        'aufs-tools',
        'git',
        'linux-image-extra-{0}'.format(kernel_release),
    ]
    apt_update()
    apt_install(packages)
    # Install docker-engine from apt.
    install_from_apt()

    opts = DockerOpts()
    render('docker.defaults', '/etc/default/docker', {'opts': opts.to_s()})

    status_set('active', 'Docker installed, cycling for extensions')
    set_state('docker.ready')

    # Make with the adding of the users to the groups
    check_call(['usermod', '-aG', 'docker', 'ubuntu'])
Example #3
0
def install():
    """
    Install the docker daemon, and supporting tooling.

    :return: None or False
    """
    # Switching runtimes causes a reinstall so remove any holds that exist.
    unhold_all()

    status_set('maintenance', 'Installing AUFS and other tools.')
    kernel_release = check_output(['uname', '-r']).rstrip()
    packages = [
        'aufs-tools',
        'git',
        'linux-image-extra-{}'.format(kernel_release.decode('utf-8')),
    ]
    apt_update()
    apt_install(packages)

    # Install docker-engine from apt.
    runtime = determine_apt_source()
    remove_state('nvidia-docker.supported')
    remove_state('nvidia-docker.installed')
    if runtime == 'upstream':
        install_from_upstream_apt()
    elif runtime == 'nvidia':
        set_state('nvidia-docker.supported')
        install_from_nvidia_apt()
        set_state('nvidia-docker.installed')
    elif runtime == 'apt':
        install_from_archive_apt()
    elif runtime == 'custom':
        if not install_from_custom_apt():
            return False  # If install fails, stop.
    else:
        hookenv.log('Unknown runtime {}'.format(runtime))
        return False

    charm_config = check_for_juju_https_proxy(config)
    validate_config(charm_config)

    opts = DockerOpts()
    render('docker.defaults', '/etc/default/docker', {
        'opts': opts.to_s(),
        'docker_runtime': runtime
    })
    render('docker.systemd', '/lib/systemd/system/docker.service',
           charm_config)
    reload_system_daemons()

    hold_all()
    hookenv.log(
        'Holding docker-engine and docker.io packages at current revision.')

    host.service_restart('docker')
    hookenv.log('Docker installed, setting "docker.ready" state.')
    set_state('docker.ready')

    # Make with the adding of the users to the groups
    check_call(['usermod', '-aG', 'docker', 'ubuntu'])
Example #4
0
def scrub_sdn_config():
    ''' If this scenario of states is true, we have likely broken a
    relationship to our once configured SDN provider. This necessitates a
    cleanup of the Docker Options for BIP and MTU of the presumed dead SDN
    interface. '''

    opts = DockerOpts()
    try:
        opts.pop('bip')
    except KeyError:
        hookenv.log('Unable to locate bip in Docker config.')
        hookenv.log('Assuming no action required.')

    try:
        opts.pop('mtu')
    except KeyError:
        hookenv.log('Unable to locate mtu in Docker config.')
        hookenv.log('Assuming no action required.')

    # This method does everything we need to ensure the bridge configuration
    # has been removed. restarting the daemon restores docker with its default
    # networking mode.
    _remove_docker_network_bridge()
    recycle_daemon()
    remove_state('docker.sdn.configured')
Example #5
0
def ingest_network_config():
    ''' When flannel configures itself on first boot, it generates an
    environment file (subnet.env).

    We will parse the data we need from this and cache in unitdata so we
    can hand it off between layers, and place in the dockeropts databag
    to configure the workload docker daemon
    '''
    db = unitdata.kv()
    opts = DockerOpts()

    if not os.path.isfile('subnet.env'):
        status_set('waiting', 'No subnet file to ingest.')
        return

    with open('subnet.env') as f:
        flannel_config = f.readlines()

    for f in flannel_config:
        if "FLANNEL_SUBNET" in f:
            value = f.split('=')[-1].strip()
            db.set('sdn_subnet', value)
            opts.add('bip', value)
        if "FLANNEL_MTU" in f:
            value = f.split('=')[1].strip()
            db.set('sdn_mtu', value)
            opts.add('mtu', value)

    set_state('sdn.available')
    set_state('flannel.configuring')
def write_drop_ins():
    """
    Write the Docker systemd drop-ins.

    :return: None
    """
    opts = DockerOpts()
    runtime = determine_apt_source()
    charm_config = check_for_juju_https_proxy(config)

    validate_config(charm_config)

    render(
        "docker.defaults",
        "/etc/default/docker",
        {
            "opts": opts.to_s(),
            "extra_opts": config("docker-opts"),
            "docker_runtime": runtime,
        },
    )
    if not os.path.isdir("/etc/systemd/system/docker.service.d/"):
        host.mkdir("/etc/systemd/system/docker.service.d/")
    render(
        "docker-daemon.conf",
        "/etc/systemd/system/docker.service.d/docker-daemon.conf",
        {},
    )
    if charm_config.get("http_proxy") or charm_config.get("https_config"):
        render(
            "http-proxy.conf",
            "/etc/systemd/system/docker.service.d/http-proxy.conf",
            charm_config,
        )
Example #7
0
def install():
    ''' Install the docker daemon, and supporting tooling '''

    # switching runtimes causes a reinstall so remove any holds that exist

    unholdall()

    # Often when building layer-docker based subordinates, you dont need to
    # incur the overhead of installing docker. This tuneable layer option
    # allows you to disable the exec of that install routine, and instead short
    # circuit immediately to docker.available, so you can charm away!
    layer_opts = layer.options('docker')
    if layer_opts['skip-install']:
        set_state('docker.available')
        set_state('docker.ready')
        return

    status_set('maintenance', 'Installing AUFS and other tools.')
    kernel_release = check_output(['uname', '-r']).rstrip()
    packages = [
        'aufs-tools',
        'git',
        'linux-image-extra-{0}'.format(kernel_release.decode('utf-8')),
    ]
    apt_update()
    apt_install(packages)

    # Install docker-engine from apt.
    runtime = determineAptSource()
    if runtime == "upstream":
        install_from_upstream_apt()
    elif runtime == "nvidia":
        install_from_nvidia_apt()
    elif runtime == "apt":
        install_from_archive_apt()
    else:
        hookenv.log('unknown runtime {0}'.format(runtime))
        return False

    validate_config()
    opts = DockerOpts()
    render('docker.defaults', '/etc/default/docker', {
        'opts': opts.to_s(),
        'docker_runtime': runtime
    })
    render('docker.systemd', '/lib/systemd/system/docker.service', config())
    reload_system_daemons()

    holdall()
    hookenv.log('Holding docker-engine and docker.io packages' +
                ' at current revision.')

    host.service_restart('docker')
    hookenv.log('Docker installed, setting "docker.ready" state.')
    set_state('docker.ready')

    # Make with the adding of the users to the groups
    check_call(['usermod', '-aG', 'docker', 'ubuntu'])
Example #8
0
def enable_client_tls():
    """
    Copy the TLS certificates in place and generate mount points for the swarm
    manager to mount the certs. This enables client-side TLS security on the
    TCP service.
    """
    if not path.exists('/etc/docker'):
        makedirs('/etc/docker')

    kv = unitdata.kv()
    cert = kv.get('tls.server.certificate')
    with open('/etc/docker/server.pem', 'w+') as f:
        f.write(cert)
    with open('/etc/docker/ca.pem', 'w+') as f:
        f.write(leader_get('certificate_authority'))

    # schenanigans
    keypath = 'easy-rsa/easyrsa3/pki/private/{}.key'
    server = getenv('JUJU_UNIT_NAME').replace('/', '_')
    if path.exists(keypath.format(server)):
        copyfile(keypath.format(server), '/etc/docker/server-key.pem')
    else:
        copyfile(keypath.format(unit_get('public-address')),
                 '/etc/docker/server-key.pem')

    opts = DockerOpts()
    config_dir = '/etc/docker'
    cert_path = '{}/server.pem'.format(config_dir)
    ca_path = '{}/ca.pem'.format(config_dir)
    key_path = '{}/server-key.pem'.format(config_dir)
    opts.add('tlscert', cert_path)
    opts.add('tlscacert', ca_path)
    opts.add('tlskey', key_path)
    opts.add('tlsverify', None)
    render('docker.defaults', '/etc/default/docker', {'opts': opts.to_s()})
Example #9
0
def recycle_daemon():
    ''' Other layers should be able to trigger a daemon restart '''
    status_set('maintenance', 'Restarting docker daemon')

    # Re-render our docker daemon template at this time... because we're
    # restarting. And its nice to play nice with others. Isn't that nice?
    opts = DockerOpts()
    render('docker.defaults', '/etc/default/docker', {'opts': opts.to_s()})

    service_restart('docker')
    remove_state('docker.restart')
Example #10
0
def bind_docker_daemon(connection_string):
    """ Bind the docker daemon to a TCP socket with TLS credentials """
    status_set("maintenance", "Configuring Docker for TCP connections")
    opts = DockerOpts()
    private_address = unit_private_ip()
    opts.add("host", "tcp://{}:2376".format(private_address))
    opts.add("host", "unix:///var/run/docker.sock")
    opts.add("cluster-advertise", "{}:2376".format(private_address))
    opts.add("cluster-store", connection_string, strict=True)
    render("docker.defaults", "/etc/default/docker", {"opts": opts.to_s()})
    service_restart("docker")
    open_port(2376)
Example #11
0
def enable_client_tls():
    """
    Copy the TLS certificates in place and generate mount points for the swarm
    manager to mount the certs. This enables client-side TLS security on the
    TCP service.
    """
    if not path.exists("/etc/docker"):
        makedirs("/etc/docker")

    kv = unitdata.kv()
    cert = kv.get("tls.server.certificate")
    with open("/etc/docker/server.pem", "w+") as f:
        f.write(cert)
    with open("/etc/docker/ca.pem", "w+") as f:
        f.write(leader_get("certificate_authority"))

    # schenanigans
    keypath = "easy-rsa/easyrsa3/pki/private/{}.key"
    server = getenv("JUJU_UNIT_NAME").replace("/", "_")
    if path.exists(keypath.format(server)):
        copyfile(keypath.format(server), "/etc/docker/server-key.pem")
    else:
        copyfile(keypath.format(unit_get("public-address")), "/etc/docker/server-key.pem")

    opts = DockerOpts()
    config_dir = "/etc/docker"
    cert_path = "{}/server.pem".format(config_dir)
    ca_path = "{}/ca.pem".format(config_dir)
    key_path = "{}/server-key.pem".format(config_dir)
    opts.add("tlscert", cert_path)
    opts.add("tlscacert", ca_path)
    opts.add("tlskey", key_path)
    opts.add("tlsverify", None)
    render("docker.defaults", "/etc/default/docker", {"opts": opts.to_s()})
Example #12
0
def render_configuration_template(service=False):
    """
    :param service: Boolean also render service file
    :return: None
    """
    opts = DockerOpts()
    config = hookenv.config

    environment_config = hookenv.env_proxy_settings()
    modified_config = dict(config())
    parsed_hosts = ""
    if environment_config is not None:
        hosts = []
        for address in environment_config.get('NO_PROXY', "").split(","):
            address = address.strip()
            try:
                net = ipaddress.ip_network(address)
                ip_addresses = [str(ip) for ip in net.hosts()]
                if ip_addresses == []:
                    hosts.append(address)
                else:
                    hosts += ip_addresses
            except ValueError:
                hosts.append(address)
        parsed_hosts = ",".join(hosts)
        environment_config.update({
            'NO_PROXY': parsed_hosts,
            'no_proxy': parsed_hosts
        })
        for key in ['http_proxy', 'https_proxy', 'no_proxy']:
            if not modified_config.get(key):
                modified_config[key] = environment_config.get(key)

    runtime = determine_apt_source()

    render(
        'docker.defaults', '/etc/default/docker', {
            'opts': opts.to_s(),
            'manual': config('docker-opts'),
            'docker_runtime': runtime
        })

    if service:
        render('docker.systemd', '/lib/systemd/system/docker.service',
               modified_config)

    write_daemon_json()
Example #13
0
def recycle_daemon():
    '''Render the docker template files and restart the docker daemon on this
    system.'''
    hookenv.log('Restarting docker service.')

    # Re-render our docker daemon template at this time... because we're
    # restarting. And its nice to play nice with others. Isn't that nice?
    opts = DockerOpts()
    render('docker.defaults', '/etc/default/docker',
           {'opts': opts.to_s(), 'manual': config('docker-opts')})
    render('docker.systemd', '/lib/systemd/system/docker.service', config())
    reload_system_daemons()
    host.service_restart('docker')

    if not _probe_runtime_availability():
        status_set('waiting', 'Container runtime not available.')
        return
Example #14
0
def manage_docker_opts(opts, remove=False):
    """
    Add or remove docker daemon options.

    Options here will be merged with configured docker-opts when layer-docker
    processes a daemon restart.

    :param opts: Dictionary keys/values; use None value if the key is a flag
    :param remove: Boolean True to remove the options; False to add them
    :return: None
    """
    try:
        docker_opts = DockerOpts()
    except Exception as e:
        hookenv.log(e)
        return

    for k, v in opts.items():
        # Always remove existing option
        if docker_opts.exists(k):
            docker_opts.pop(k)
        if not remove:
            docker_opts.add(k, v)
    hookenv.log('DockerOpts daemon options changed. Requesting a restart.')
    # State will be removed by layer-docker after restart
    set_state('docker.restart')
Example #15
0
def render_configuration_template(service=False):
    """
    :param service: Boolean also render service file
    :return: None
    """
    opts = DockerOpts()
    config = hookenv.config
    runtime = determine_apt_source()

    render(
        'docker.defaults', '/etc/default/docker', {
            'opts': opts.to_s(),
            'manual': config('docker-opts'),
            'docker_runtime': runtime
        })

    if service:
        render('docker.systemd', '/lib/systemd/system/docker.service',
               config())
Example #16
0
def recycle_daemon():
    '''Render the docker template files and restart the docker daemon on this
    system.'''
    hookenv.log('Restarting docker service.')

    # Re-render our docker daemon template at this time... because we're
    # restarting. And its nice to play nice with others. Isn't that nice?
    opts = DockerOpts()
    render('docker.defaults', '/etc/default/docker', {
        'opts': opts.to_s(),
        'manual': config('docker-opts')
    })
    render('docker.systemd', '/lib/systemd/system/docker.service', config())
    reload_system_daemons()
    host.service_restart('docker')

    if not _probe_runtime_availability():
        status_set('waiting', 'Container runtime not available.')
        return
Example #17
0
def scrub_sdn_config():
    """
    If this scenario of states is true, we have likely broken a
    relationship to our once configured SDN provider. This necessitates a
    cleanup of the Docker Options for BIP and MTU of the presumed dead SDN
    interface.

    :return: None
    """
    opts = DockerOpts()
    try:
        opts.pop('bip')
    except KeyError:
        hookenv.log('Unable to locate bip in Docker config.')
        hookenv.log('Assuming no action required.')

    try:
        opts.pop('mtu')
    except KeyError:
        hookenv.log('Unable to locate mtu in Docker config.')
        hookenv.log('Assuming no action required.')

    # This method does everything we need to ensure the bridge configuration
    # has been removed. restarting the daemon restores docker with its default
    # networking mode.
    _remove_docker_network_bridge()
    recycle_daemon()
    remove_state('docker.sdn.configured')
Example #18
0
def install():
    ''' Install the docker daemon, and supporting tooling '''
    # Often when building layer-docker based subordinates, you dont need to
    # incur the overhead of installing docker. This tuneable layer option
    # allows you to disable the exec of that install routine, and instead short
    # circuit immediately to docker.available, so you can charm away!
    layer_opts = layer.options('docker')
    if layer_opts['skip-install']:
        set_state('docker.available')
        set_state('docker.ready')
        return

    status_set('maintenance', 'Installing AUFS and other tools.')
    kernel_release = check_output(['uname', '-r']).rstrip()
    packages = [
        'aufs-tools',
        'git',
        'linux-image-extra-{0}'.format(kernel_release.decode('utf-8')),
    ]
    apt_update()
    apt_install(packages)
    # Install docker-engine from apt.
    if config('install_from_upstream'):
        install_from_upstream_apt()
    else:
        install_from_archive_apt()

    opts = DockerOpts()
    render('docker.defaults', '/etc/default/docker', {'opts': opts.to_s()})
    render('docker.systemd', '/lib/systemd/system/docker.service', config())
    reload_system_daemons()

    hookenv.log('Docker installed, setting "docker.ready" state.')
    set_state('docker.ready')

    # Make with the adding of the users to the groups
    check_call(['usermod', '-aG', 'docker', 'ubuntu'])
Example #19
0
def swarm_etcd_cluster_setup(etcd):
    """
    Expose the Docker TCP port, and begin swarm cluster configuration. Always
    leading with the agent, connecting to the discovery service, then follow
    up with the manager container on the leader node.
    """
    opts = DockerOpts()
    # capture and place etcd TLS certificates
    certs = etcd.get_client_credentials()
    unit_name = getenv('JUJU_UNIT_NAME').replace('/', '-')
    cert_path = '/etc/ssl/{}'.format(unit_name)

    # if we have all the keys required, save them on disk
    if certs['client_ca'] and certs['client_key'] and certs['client_cert']:
        if not path.exists(cert_path):
            makedirs(cert_path)
        ca = "{}/client-ca.pem".format(cert_path)
        cert = "{}/client-cert.pem".format(cert_path)
        key = "{}/client-key.pem".format(cert_path)

        etcd.save_client_credentials(key, cert, ca)

    # format the connection string based on presence of encryption in the
    # connection string. Docker is the only known suite of tooling to use
    # the etcd:// protocol uri... dubious

    secure_discovery = 'https' in etcd.connection_string()
    if secure_discovery:
        con_string = etcd.connection_string().replace('https', 'etcd')
        ccert = 'kv.certfile={}'.format(cert)
        ckey = 'kv.keyfile={}'.format(key)
        cca = 'kv.cacertfile={}'.format(ca)
        opts.add('cluster-store-opt', ccert)
        opts.add('cluster-store-opt', ckey)
        opts.add('cluster-store-opt', cca)
    else:
        con_string = etcd.connection_string().replace('http', 'etcd')

    bind_docker_daemon(con_string)

    if secure_discovery:
        start_swarm(con_string, cert_path)
    else:
        start_swarm(con_string)

    status_set('active', 'Swarm configured. Happy swarming')
Example #20
0
def swarm_etcd_cluster_setup(etcd):
    """
    Expose the Docker TCP port, and begin swarm cluster configuration. Always
    leading with the agent, connecting to the discovery service, then follow
    up with the manager container on the leader node.
    """
    opts = DockerOpts()
    # capture and place etcd TLS certificates
    certs = etcd.get_client_credentials()
    unit_name = getenv("JUJU_UNIT_NAME").replace("/", "-")
    cert_path = "/etc/ssl/{}".format(unit_name)

    # if we have all the keys required, save them on disk
    if certs["client_ca"] and certs["client_key"] and certs["client_cert"]:
        if not path.exists(cert_path):
            makedirs(cert_path)
        ca = "{}/client-ca.pem".format(cert_path)
        cert = "{}/client-cert.pem".format(cert_path)
        key = "{}/client-key.pem".format(cert_path)

        etcd.save_client_credentials(key, cert, ca)

    # format the connection string based on presence of encryption in the
    # connection string. Docker is the only known suite of tooling to use
    # the etcd:// protocol uri... dubious

    secure_discovery = "https" in etcd.connection_string()
    if secure_discovery:
        con_string = etcd.connection_string().replace("https", "etcd")
        ccert = "kv.certfile={}".format(cert)
        ckey = "kv.keyfile={}".format(key)
        cca = "kv.cacertfile={}".format(ca)
        opts.add("cluster-store-opt", ccert)
        opts.add("cluster-store-opt", ckey)
        opts.add("cluster-store-opt", cca)
    else:
        con_string = etcd.connection_string().replace("http", "etcd")

    bind_docker_daemon(con_string)

    if secure_discovery:
        start_swarm(con_string, cert_path)
    else:
        start_swarm(con_string)

    status_set("active", "Swarm configured. Happy swarming")
def manage_docker_opts(opts, remove=False):
    '''Add or remove docker daemon options.

    Options here will be merged with configured docker-opts when layer-docker
    processes a daemon restart.

    :param: dict opts: option keys/values; use None value if the key is a flag
    :param: bool remove: True to remove the options; False to add them
    '''
    docker_opts = DockerOpts()
    for k, v in opts.items():
        # Always remove existing option
        if docker_opts.exists(k):
            docker_opts.pop(k)
        if not remove:
            docker_opts.add(k, v)
    hookenv.log('DockerOpts daemon options changed. Requesting a restart.')
    # State will be removed by layer-docker after restart
    set_state('docker.restart')
Example #22
0
def container_sdn_setup(sdn):
    ''' Receive the information from the SDN plugin, and render the docker
    engine options. '''
    sdn_config = sdn.get_sdn_config()
    bind_ip = sdn_config['subnet']
    mtu = sdn_config['mtu']
    if data_changed('bip', bind_ip) or data_changed('mtu', mtu):
        status_set('maintenance', 'Configuring container runtime with SDN.')
        opts = DockerOpts()
        # This is a great way to misconfigure a docker daemon. Remove the
        # existing bind ip and mtu values of the SDN
        if opts.exists('bip'):
            opts.pop('bip')
        if opts.exists('mtu'):
            opts.pop('mtu')
        opts.add('bip', bind_ip)
        opts.add('mtu', mtu)
        _remove_docker_network_bridge()
        set_state('docker.sdn.configured')
Example #23
0
def bind_docker_daemon(connection_string):
    """ Bind the docker daemon to a TCP socket with TLS credentials """
    status_set('maintenance', 'Configuring Docker for TCP connections')
    opts = DockerOpts()
    private_address = unit_private_ip()
    opts.add('host', 'tcp://{}:2376'.format(private_address))
    opts.add('host', 'unix:///var/run/docker.sock')
    opts.add('cluster-advertise', '{}:2376'.format(private_address))
    opts.add('cluster-store', connection_string, strict=True)
    render('docker.defaults', '/etc/default/docker', {'opts': opts.to_s()})
    service_restart('docker')
    open_port(2376)
Example #24
0
def container_sdn_setup(sdn):
    """
    Receive the information from the SDN plugin, and render the docker
    engine options.

    :param sdn: SDNPluginProvider
    :return: None
    """
    sdn_config = sdn.get_sdn_config()
    bind_ip = sdn_config['subnet']
    mtu = sdn_config['mtu']
    if data_changed('bip', bind_ip) or data_changed('mtu', mtu):
        status_set('maintenance', 'Configuring container runtime with SDN.')
        opts = DockerOpts()
        # This is a great way to misconfigure a docker daemon. Remove the
        # existing bind ip and mtu values of the SDN
        if opts.exists('bip'):
            opts.pop('bip')
        if opts.exists('mtu'):
            opts.pop('mtu')
        opts.add('bip', bind_ip)
        opts.add('mtu', mtu)
        _remove_docker_network_bridge()
        set_state('docker.sdn.configured')