From d06c2539c7abd4fd544df2d1e671e581ea371c4a Mon Sep 17 00:00:00 2001 From: Maksim Chizhov Date: Mon, 13 Apr 2015 16:23:28 +0300 Subject: [PATCH] Reduce image size produced by `machine_image` resource (chef/chef-provisioning-docker#26) Signed-off-by: Maksim Chizhov --- .../docker_driver/docker_container_machine.rb | 3 + .../docker_convergence_strategy.rb | 96 +++++++++++++++++++ lib/chef/provisioning/docker_driver/driver.rb | 9 +- 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 lib/chef/provisioning/docker_driver/docker_convergence_strategy.rb diff --git a/lib/chef/provisioning/docker_driver/docker_container_machine.rb b/lib/chef/provisioning/docker_driver/docker_container_machine.rb index 53de04b..098150c 100644 --- a/lib/chef/provisioning/docker_driver/docker_container_machine.rb +++ b/lib/chef/provisioning/docker_driver/docker_container_machine.rb @@ -20,6 +20,9 @@ def initialize(machine_spec, transport, convergence_strategy, opts = {}) @transport = transport end + # Expose volumes to be able to access it from DockerConvergenceStrategy. + attr_reader :volumes + def execute_always(command, options = {}) transport.execute(command, { :read_only => true }.merge(options)) end diff --git a/lib/chef/provisioning/docker_driver/docker_convergence_strategy.rb b/lib/chef/provisioning/docker_driver/docker_convergence_strategy.rb new file mode 100644 index 0000000..99dd863 --- /dev/null +++ b/lib/chef/provisioning/docker_driver/docker_convergence_strategy.rb @@ -0,0 +1,96 @@ +require 'chef/provisioning/convergence_strategy/precreate_chef_objects' +require 'chef/provisioning/convergence_strategy/install_cached' + +# We don't want to install chef-client omnibus into container. Instead we'll +# mount volume with exiting installation or install it on that volume if missing. +class Chef +module Provisioning +module DockerDriver + class ConvergenceStrategy < Chef::Provisioning::ConvergenceStrategy::InstallCached + + # Initializes instance, sets volume to install chef-client to + # "/tmp/opt/chef:/opt/chef" by default. + def initialize(convergence_options, config) + super(convergence_options, config) + # Chef obmibus installation directory on host machine. + @chef_volume = (convergence_options[:chef_volume] || '/tmp/opt/chef') + ":/opt/chef" + end + + # Inlike Chef::Provisioning::ConvergenceStrategy::InstallCached, will not + # upload package to machine, but mount it as volume instead. + def setup_convergence(action_handler, machine) + # We still need some basic setup from InstallCached parent class. + PrecreateChefObjects.instance_method(:setup_convergence).bind(self).call(action_handler, machine) + + # Check for existing chef client. + version = machine.execute_always('chef-client -v', :volumes => [@chef_volume]) + + # Don't do install/upgrade if a chef client exists and + # no chef version is defined by user configs or + # the chef client's version already matches user config + if version.exitstatus == 0 + version = version.stdout.strip + if !chef_version + return + elsif version =~ /Chef: #{chef_version}$/ + Chef::Log.debug "Already installed chef version #{version}" + return + elsif version.include?(chef_version) + Chef::Log.warn "Installed chef version #{version} contains desired version #{chef_version}. " + + "If you see this message on consecutive chef runs tighten your desired version constraint to prevent " + + "multiple convergence." + end + end + + # Install chef client + platform, platform_version, machine_architecture = machine.detect_os(action_handler) + package_file = download_package_for_platform(action_handler, machine, platform, platform_version, machine_architecture) + remote_package_file = "#{@tmp_dir}/#{File.basename(package_file)}" + install_package(action_handler, machine, remote_package_file) + end + + # Converge machine using volumes from machine options and chef installalation + # from @chef_volume (default "/tmp/opt/chef:/opt/chef"). + # This implementation also allows to mount volumes during recipes execution + # including machine images creation. + def converge(action_handler, machine) + Chef::Log.debug("Converge machine using machine volumes: #{machine.volumes}") + PrecreateChefObjects.instance_method(:converge).bind(self).call(action_handler, machine) + + action_handler.open_stream(machine.node['name']) do |stdout| + action_handler.open_stream(machine.node['name']) do |stderr| + command_line = "chef-client" + command_line << " -l #{config[:log_level].to_s}" if config[:log_level] + machine.execute(action_handler, command_line, + :stream_stdout => stdout, + :stream_stderr => stderr, + :timeout => @chef_client_timeout, + # In case we've specified volumes in machine options, it needs + # to be accessible during converge too. + :volumes => Array(machine.volumes) << @chef_volume) + end + end + end + + # Installs chef-client omnibus inside container on mounted volume + # (see @chef_volume), package is also mounted as ro volume. + def install_package(action_handler, machine, remote_package_file) + + local_package_file = File.join(@package_cache_path, File.basename(remote_package_file)) + opts = {:volumes => [@chef_volume, "#{local_package_file}:#{remote_package_file}:ro"]} + extension = File.extname(remote_package_file) + result = case extension + when '.rpm' + machine.execute(action_handler, "rpm -Uvh --oldpackage --replacepkgs \"#{remote_package_file}\"", opts) + when '.deb' + machine.execute(action_handler, "dpkg -i \"#{remote_package_file}\"", opts) + when '.sh' + machine.execute(action_handler, "sh \"#{remote_package_file}\"", opts) + else + raise "Unknown package extension '#{extension}' for file #{remote_package_file}" + end + end + end +end +end +end diff --git a/lib/chef/provisioning/docker_driver/driver.rb b/lib/chef/provisioning/docker_driver/driver.rb index b3be326..a281bb2 100644 --- a/lib/chef/provisioning/docker_driver/driver.rb +++ b/lib/chef/provisioning/docker_driver/driver.rb @@ -3,7 +3,7 @@ require 'chef/provisioning/docker_driver/version' require 'chef/provisioning/docker_driver/docker_transport' require 'chef/provisioning/docker_driver/docker_container_machine' -require 'chef/provisioning/convergence_strategy/install_cached' +require 'chef/provisioning/docker_driver/docker_convergence_strategy' require 'chef/provisioning/convergence_strategy/no_converge' require 'chef/mash' @@ -95,7 +95,7 @@ def build_container(machine_spec, machine_options) docker_options = machine_options[:docker_options] base_image = docker_options[:base_image] - if !base_image + if base_image.nil? Chef::Log.debug("No base images specified in docker options.") base_image = base_image_for(machine_spec) end @@ -176,7 +176,7 @@ def destroy_machine(action_handler, machine_spec, machine_options) Chef::Log.debug("Removing #{container_name}") container.delete - if !machine_spec.attrs[:keep_image] && !machine_options[:keep_image] + unless machine_spec.attrs[:keep_image] || machine_options[:keep_image] Chef::Log.debug("Destroying image: chef:#{container_name}") image = Docker::Image.get("chef:#{container_name}") image.delete @@ -250,8 +250,9 @@ def machine_for(machine_spec, machine_options, base_image_name) def convergence_strategy_for(machine_spec, machine_options) @unix_convergence_strategy ||= begin - Chef::Provisioning::ConvergenceStrategy::InstallCached. + Chef::Provisioning::DockerDriver::ConvergenceStrategy. new(machine_options[:convergence_options], config) + end end