github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/worker/provisioner/container_initialisation.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"sync/atomic"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils"
    13  	"github.com/juju/utils/exec"
    14  	"github.com/juju/utils/fslock"
    15  
    16  	"github.com/juju/juju/agent"
    17  	apiprovisioner "github.com/juju/juju/api/provisioner"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/container"
    20  	"github.com/juju/juju/container/kvm"
    21  	"github.com/juju/juju/container/lxc"
    22  	"github.com/juju/juju/container/lxd"
    23  	"github.com/juju/juju/environs"
    24  	"github.com/juju/juju/instance"
    25  	"github.com/juju/juju/state"
    26  	"github.com/juju/juju/watcher"
    27  	"github.com/juju/juju/worker"
    28  )
    29  
    30  // ContainerSetup is a StringsWatchHandler that is notified when containers
    31  // are created on the given machine. It will set up the machine to be able
    32  // to create containers and start a suitable provisioner.
    33  type ContainerSetup struct {
    34  	runner                worker.Runner
    35  	supportedContainers   []instance.ContainerType
    36  	imageURLGetter        container.ImageURLGetter
    37  	provisioner           *apiprovisioner.State
    38  	machine               *apiprovisioner.Machine
    39  	config                agent.Config
    40  	initLock              *fslock.Lock
    41  	addressableContainers bool
    42  	enableNAT             bool
    43  	lxcDefaultMTU         int
    44  
    45  	// Save the workerName so the worker thread can be stopped.
    46  	workerName string
    47  	// setupDone[containerType] is non zero if the container setup has been invoked
    48  	// for that container type.
    49  	setupDone map[instance.ContainerType]*int32
    50  	// The number of provisioners started. Once all necessary provisioners have
    51  	// been started, the container watcher can be stopped.
    52  	numberProvisioners int32
    53  }
    54  
    55  // ContainerSetupParams are used to initialise a container setup handler.
    56  type ContainerSetupParams struct {
    57  	Runner              worker.Runner
    58  	WorkerName          string
    59  	SupportedContainers []instance.ContainerType
    60  	ImageURLGetter      container.ImageURLGetter
    61  	Machine             *apiprovisioner.Machine
    62  	Provisioner         *apiprovisioner.State
    63  	Config              agent.Config
    64  	InitLock            *fslock.Lock
    65  }
    66  
    67  // NewContainerSetupHandler returns a StringsWatchHandler which is notified when
    68  // containers are created on the given machine.
    69  func NewContainerSetupHandler(params ContainerSetupParams) watcher.StringsHandler {
    70  	return &ContainerSetup{
    71  		runner:              params.Runner,
    72  		imageURLGetter:      params.ImageURLGetter,
    73  		machine:             params.Machine,
    74  		supportedContainers: params.SupportedContainers,
    75  		provisioner:         params.Provisioner,
    76  		config:              params.Config,
    77  		workerName:          params.WorkerName,
    78  		initLock:            params.InitLock,
    79  	}
    80  }
    81  
    82  // SetUp is defined on the StringsWatchHandler interface.
    83  func (cs *ContainerSetup) SetUp() (watcher watcher.StringsWatcher, err error) {
    84  	// Set up the semaphores for each container type.
    85  	cs.setupDone = make(map[instance.ContainerType]*int32, len(instance.ContainerTypes))
    86  	for _, containerType := range instance.ContainerTypes {
    87  		zero := int32(0)
    88  		cs.setupDone[containerType] = &zero
    89  	}
    90  	// Listen to all container lifecycle events on our machine.
    91  	if watcher, err = cs.machine.WatchAllContainers(); err != nil {
    92  		return nil, err
    93  	}
    94  	return watcher, nil
    95  }
    96  
    97  // Handle is called whenever containers change on the machine being watched.
    98  // Machines start out with no containers so the first time Handle is called,
    99  // it will be because a container has been added.
   100  func (cs *ContainerSetup) Handle(_ <-chan struct{}, containerIds []string) (resultError error) {
   101  	// Consume the initial watcher event.
   102  	if len(containerIds) == 0 {
   103  		return nil
   104  	}
   105  
   106  	logger.Infof("initial container setup with ids: %v", containerIds)
   107  	for _, id := range containerIds {
   108  		containerType := state.ContainerTypeFromId(id)
   109  		// If this container type has been dealt with, do nothing.
   110  		if atomic.LoadInt32(cs.setupDone[containerType]) != 0 {
   111  			continue
   112  		}
   113  		if err := cs.initialiseAndStartProvisioner(containerType); err != nil {
   114  			logger.Errorf("starting container provisioner for %v: %v", containerType, err)
   115  			// Just because dealing with one type of container fails, we won't exit the entire
   116  			// function because we still want to try and start other container types. So we
   117  			// take note of and return the first such error.
   118  			if resultError == nil {
   119  				resultError = err
   120  			}
   121  		}
   122  	}
   123  	return resultError
   124  }
   125  
   126  func (cs *ContainerSetup) initialiseAndStartProvisioner(containerType instance.ContainerType) (resultError error) {
   127  	// Flag that this container type has been handled.
   128  	atomic.StoreInt32(cs.setupDone[containerType], 1)
   129  
   130  	defer func() {
   131  		if resultError != nil {
   132  			logger.Warningf("not stopping machine agent container watcher due to error: %v", resultError)
   133  			return
   134  		}
   135  		if atomic.AddInt32(&cs.numberProvisioners, 1) == int32(len(cs.supportedContainers)) {
   136  			// We only care about the initial container creation.
   137  			// This worker has done its job so stop it.
   138  			// We do not expect there will be an error, and there's not much we can do anyway.
   139  			if err := cs.runner.StopWorker(cs.workerName); err != nil {
   140  				logger.Warningf("stopping machine agent container watcher: %v", err)
   141  			}
   142  		}
   143  	}()
   144  
   145  	logger.Debugf("setup and start provisioner for %s containers", containerType)
   146  	toolsFinder := getToolsFinder(cs.provisioner)
   147  	initialiser, broker, toolsFinder, err := cs.getContainerArtifacts(containerType, toolsFinder)
   148  	if err != nil {
   149  		return errors.Annotate(err, "initialising container infrastructure on host machine")
   150  	}
   151  	if err := cs.runInitialiser(containerType, initialiser); err != nil {
   152  		return errors.Annotate(err, "setting up container dependencies on host machine")
   153  	}
   154  	return StartProvisioner(cs.runner, containerType, cs.provisioner, cs.config, broker, toolsFinder)
   155  }
   156  
   157  const etcDefaultLXCNet = `
   158  # Modified by Juju to enable addressable LXC containers.
   159  USE_LXC_BRIDGE="true"
   160  LXC_BRIDGE="lxcbr0"
   161  LXC_ADDR="10.0.3.1"
   162  LXC_NETMASK="255.255.255.0"
   163  LXC_NETWORK="10.0.3.0/24"
   164  LXC_DHCP_RANGE="10.0.3.2,10.0.3.254,infinite"
   165  LXC_DHCP_MAX="253"
   166  `
   167  
   168  var etcDefaultLXCNetPath = "/etc/default/lxc-net"
   169  
   170  // maybeOverrideDefaultLXCNet writes a modified version of
   171  // /etc/default/lxc-net file on the host before installing the lxc
   172  // package, if we're about to start an addressable LXC container. This
   173  // is needed to guarantee stable statically assigned IP addresses for
   174  // the container. See also runInitialiser.
   175  func maybeOverrideDefaultLXCNet(containerType instance.ContainerType, addressable bool) error {
   176  	if containerType != instance.LXC || !addressable {
   177  		// Nothing to do.
   178  		return nil
   179  	}
   180  
   181  	err := utils.AtomicWriteFile(etcDefaultLXCNetPath, []byte(etcDefaultLXCNet), 0644)
   182  	if err != nil {
   183  		return errors.Annotatef(err, "cannot write %q", etcDefaultLXCNetPath)
   184  	}
   185  	return nil
   186  }
   187  
   188  // runInitialiser runs the container initialiser with the initialisation hook held.
   189  func (cs *ContainerSetup) runInitialiser(containerType instance.ContainerType, initialiser container.Initialiser) error {
   190  	logger.Debugf("running initialiser for %s containers", containerType)
   191  	if err := cs.initLock.Lock(fmt.Sprintf("initialise-%s", containerType)); err != nil {
   192  		return errors.Annotate(err, "failed to acquire initialization lock")
   193  	}
   194  	defer cs.initLock.Unlock()
   195  
   196  	// Only tweak default LXC network config when address allocation
   197  	// feature flag is enabled.
   198  	if environs.AddressAllocationEnabled(cs.config.Value(agent.ProviderType)) {
   199  		// In order to guarantee stable statically assigned IP addresses
   200  		// for LXC containers, we need to install a custom version of
   201  		// /etc/default/lxc-net before we install the lxc package. The
   202  		// custom version of lxc-net is almost the same as the original,
   203  		// but the defined LXC_DHCP_RANGE (used by dnsmasq to give away
   204  		// 10.0.3.x addresses to containers bound to lxcbr0) has infinite
   205  		// lease time. This is necessary, because with the default lease
   206  		// time of 1h, dhclient running inside each container will request
   207  		// a renewal from dnsmasq and replace our statically configured IP
   208  		// address within an hour after starting the container.
   209  		err := maybeOverrideDefaultLXCNet(containerType, cs.addressableContainers)
   210  		if err != nil {
   211  			return errors.Trace(err)
   212  		}
   213  	}
   214  
   215  	if err := initialiser.Initialise(); err != nil {
   216  		return errors.Trace(err)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  // TearDown is defined on the StringsWatchHandler interface.
   223  func (cs *ContainerSetup) TearDown() error {
   224  	// Nothing to do here.
   225  	return nil
   226  }
   227  
   228  // getContainerArtifacts returns type-specific interfaces for
   229  // managing containers.
   230  //
   231  // The ToolsFinder passed in may be replaced or wrapped to
   232  // enforce container-specific constraints.
   233  func (cs *ContainerSetup) getContainerArtifacts(
   234  	containerType instance.ContainerType, toolsFinder ToolsFinder,
   235  ) (
   236  	container.Initialiser,
   237  	environs.InstanceBroker,
   238  	ToolsFinder,
   239  	error,
   240  ) {
   241  	var initialiser container.Initialiser
   242  	var broker environs.InstanceBroker
   243  
   244  	managerConfig, err := containerManagerConfig(containerType, cs.provisioner, cs.config)
   245  	if err != nil {
   246  		return nil, nil, nil, err
   247  	}
   248  
   249  	// Override default MTU for LXC NICs, if needed.
   250  	if mtu := managerConfig.PopValue(container.ConfigLXCDefaultMTU); mtu != "" {
   251  		value, err := strconv.Atoi(mtu)
   252  		if err != nil {
   253  			return nil, nil, nil, errors.Trace(err)
   254  		}
   255  		logger.Infof("setting MTU to %v for all LXC containers' interfaces", value)
   256  		cs.lxcDefaultMTU = value
   257  	}
   258  
   259  	// Enable IP forwarding and ARP proxying if needed.
   260  	if ipfwd := managerConfig.PopValue(container.ConfigIPForwarding); ipfwd != "" {
   261  		if err := setIPAndARPForwarding(true); err != nil {
   262  			return nil, nil, nil, errors.Trace(err)
   263  		}
   264  		cs.addressableContainers = true
   265  		logger.Infof("enabled IP forwarding and ARP proxying for containers")
   266  	}
   267  
   268  	// Enable NAT if needed.
   269  	if nat := managerConfig.PopValue(container.ConfigEnableNAT); nat != "" {
   270  		cs.enableNAT = true
   271  		logger.Infof("enabling NAT for containers")
   272  	}
   273  
   274  	switch containerType {
   275  	case instance.LXC:
   276  		series, err := cs.machine.Series()
   277  		if err != nil {
   278  			return nil, nil, nil, err
   279  		}
   280  
   281  		initialiser = lxc.NewContainerInitialiser(series)
   282  		broker, err = NewLxcBroker(
   283  			cs.provisioner,
   284  			cs.config,
   285  			managerConfig,
   286  			cs.imageURLGetter,
   287  			cs.enableNAT,
   288  			cs.lxcDefaultMTU,
   289  		)
   290  		if err != nil {
   291  			return nil, nil, nil, err
   292  		}
   293  
   294  		// LXC containers must have the same architecture as the host.
   295  		// We should call through to the finder since the version of
   296  		// tools running on the host may not match, but we want to
   297  		// override the arch constraint with the arch of the host.
   298  		toolsFinder = hostArchToolsFinder{toolsFinder}
   299  
   300  	case instance.KVM:
   301  		initialiser = kvm.NewContainerInitialiser()
   302  		broker, err = NewKvmBroker(
   303  			cs.provisioner,
   304  			cs.config,
   305  			managerConfig,
   306  			cs.enableNAT,
   307  		)
   308  		if err != nil {
   309  			logger.Errorf("failed to create new kvm broker")
   310  			return nil, nil, nil, err
   311  		}
   312  	case instance.LXD:
   313  		series, err := cs.machine.Series()
   314  		if err != nil {
   315  			return nil, nil, nil, err
   316  		}
   317  
   318  		initialiser = lxd.NewContainerInitialiser(series)
   319  		namespace := maybeGetManagerConfigNamespaces(managerConfig)
   320  		manager, err := lxd.NewContainerManager(managerConfig)
   321  		if err != nil {
   322  			return nil, nil, nil, err
   323  		}
   324  		broker, err = NewLxdBroker(
   325  			cs.provisioner,
   326  			manager,
   327  			cs.config,
   328  			namespace,
   329  			cs.enableNAT,
   330  		)
   331  		if err != nil {
   332  			logger.Errorf("failed to create new lxd broker")
   333  			return nil, nil, nil, err
   334  		}
   335  	default:
   336  		return nil, nil, nil, fmt.Errorf("unknown container type: %v", containerType)
   337  	}
   338  
   339  	return initialiser, broker, toolsFinder, nil
   340  }
   341  
   342  func containerManagerConfig(
   343  	containerType instance.ContainerType,
   344  	provisioner *apiprovisioner.State,
   345  	agentConfig agent.Config,
   346  ) (container.ManagerConfig, error) {
   347  	// Ask the provisioner for the container manager configuration.
   348  	managerConfigResult, err := provisioner.ContainerManagerConfig(
   349  		params.ContainerManagerConfigParams{Type: containerType},
   350  	)
   351  	if err != nil {
   352  		return nil, err
   353  	}
   354  	// If a namespace is specified, that should instead be used as the config name.
   355  	if namespace := agentConfig.Value(agent.Namespace); namespace != "" {
   356  		managerConfigResult.ManagerConfig[container.ConfigName] = namespace
   357  	}
   358  	managerConfig := container.ManagerConfig(managerConfigResult.ManagerConfig)
   359  
   360  	return managerConfig, nil
   361  }
   362  
   363  // Override for testing.
   364  var (
   365  	StartProvisioner = startProvisionerWorker
   366  
   367  	sysctlConfig = "/etc/sysctl.conf"
   368  )
   369  
   370  const (
   371  	ipForwardSysctlKey = "net.ipv4.ip_forward"
   372  	arpProxySysctlKey  = "net.ipv4.conf.all.proxy_arp"
   373  )
   374  
   375  // startProvisionerWorker kicks off a provisioner task responsible for creating containers
   376  // of the specified type on the machine.
   377  func startProvisionerWorker(
   378  	runner worker.Runner,
   379  	containerType instance.ContainerType,
   380  	provisioner *apiprovisioner.State,
   381  	config agent.Config,
   382  	broker environs.InstanceBroker,
   383  	toolsFinder ToolsFinder,
   384  ) error {
   385  
   386  	workerName := fmt.Sprintf("%s-provisioner", containerType)
   387  	// The provisioner task is created after a container record has
   388  	// already been added to the machine. It will see that the
   389  	// container does not have an instance yet and create one.
   390  	return runner.StartWorker(workerName, func() (worker.Worker, error) {
   391  		w, err := NewContainerProvisioner(containerType, provisioner, config, broker, toolsFinder)
   392  		if err != nil {
   393  			return nil, errors.Trace(err)
   394  		}
   395  		return w, nil
   396  	})
   397  }
   398  
   399  // setIPAndARPForwarding enables or disables IP and ARP forwarding on
   400  // the machine. This is needed when the machine needs to host
   401  // addressable containers.
   402  var setIPAndARPForwarding = func(enabled bool) error {
   403  	val := "0"
   404  	if enabled {
   405  		val = "1"
   406  	}
   407  
   408  	runCmds := func(keyAndVal string) (err error) {
   409  
   410  		defer errors.DeferredAnnotatef(&err, "cannot set %s", keyAndVal)
   411  
   412  		commands := []string{
   413  			// Change it immediately:
   414  			fmt.Sprintf("sysctl -w %s", keyAndVal),
   415  
   416  			// Change it also on next boot:
   417  			fmt.Sprintf("echo '%s' | tee -a %s", keyAndVal, sysctlConfig),
   418  		}
   419  		for _, cmd := range commands {
   420  			result, err := exec.RunCommands(exec.RunParams{Commands: cmd})
   421  			if err != nil {
   422  				return errors.Trace(err)
   423  			}
   424  			logger.Debugf(
   425  				"command %q returned: code: %d, stdout: %q, stderr: %q",
   426  				cmd, result.Code, string(result.Stdout), string(result.Stderr),
   427  			)
   428  			if result.Code != 0 {
   429  				return errors.Errorf("unexpected exit code %d", result.Code)
   430  			}
   431  		}
   432  		return nil
   433  	}
   434  
   435  	err := runCmds(fmt.Sprintf("%s=%s", ipForwardSysctlKey, val))
   436  	if err != nil {
   437  		return err
   438  	}
   439  	return runCmds(fmt.Sprintf("%s=%s", arpProxySysctlKey, val))
   440  }