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