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 }