github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/network_hook.go (about) 1 package allocrunner 2 3 import ( 4 "context" 5 "fmt" 6 7 hclog "github.com/hashicorp/go-hclog" 8 "github.com/hashicorp/nomad/client/taskenv" 9 "github.com/hashicorp/nomad/nomad/structs" 10 "github.com/hashicorp/nomad/plugins/drivers" 11 "github.com/miekg/dns" 12 ) 13 14 const ( 15 // dockerNetSpecLabelKey is the label added when we create a pause 16 // container to own the network namespace, and the NetworkIsolationSpec we 17 // get back from CreateNetwork has this label set as the container ID. 18 // We'll use this to generate a hostname for the task in the event the user 19 // did not specify a custom one. Please see dockerNetSpecHostnameKey. 20 dockerNetSpecLabelKey = "docker_sandbox_container_id" 21 22 // dockerNetSpecHostnameKey is the label added when we create a pause 23 // container and the task group network include a user supplied hostname 24 // parameter. 25 dockerNetSpecHostnameKey = "docker_sandbox_hostname" 26 ) 27 28 type networkIsolationSetter interface { 29 SetNetworkIsolation(*drivers.NetworkIsolationSpec) 30 } 31 32 // allocNetworkIsolationSetter is a shim to allow the alloc network hook to 33 // set the alloc network isolation configuration without full access 34 // to the alloc runner 35 type allocNetworkIsolationSetter struct { 36 ar *allocRunner 37 } 38 39 func (a *allocNetworkIsolationSetter) SetNetworkIsolation(n *drivers.NetworkIsolationSpec) { 40 for _, tr := range a.ar.tasks { 41 tr.SetNetworkIsolation(n) 42 } 43 } 44 45 type networkStatusSetter interface { 46 SetNetworkStatus(*structs.AllocNetworkStatus) 47 } 48 49 // networkHook is an alloc lifecycle hook that manages the network namespace 50 // for an alloc 51 type networkHook struct { 52 // isolationSetter is a callback to set the network isolation spec when after the 53 // network is created 54 isolationSetter networkIsolationSetter 55 56 // statusSetter is a callback to the alloc runner to set the network status once 57 // network setup is complete 58 networkStatusSetter networkStatusSetter 59 60 // manager is used when creating the network namespace. This defaults to 61 // bind mounting a network namespace descritor under /var/run/netns but 62 // can be created by a driver if nessicary 63 manager drivers.DriverNetworkManager 64 65 // alloc should only be read from 66 alloc *structs.Allocation 67 68 // spec described the network namespace and is syncronized by specLock 69 spec *drivers.NetworkIsolationSpec 70 71 // networkConfigurator configures the network interfaces, routes, etc once 72 // the alloc network has been created 73 networkConfigurator NetworkConfigurator 74 75 // taskEnv is used to perform interpolation within the network blocks. 76 taskEnv *taskenv.TaskEnv 77 78 logger hclog.Logger 79 } 80 81 func newNetworkHook(logger hclog.Logger, 82 ns networkIsolationSetter, 83 alloc *structs.Allocation, 84 netManager drivers.DriverNetworkManager, 85 netConfigurator NetworkConfigurator, 86 networkStatusSetter networkStatusSetter, 87 taskEnv *taskenv.TaskEnv, 88 ) *networkHook { 89 return &networkHook{ 90 isolationSetter: ns, 91 networkStatusSetter: networkStatusSetter, 92 alloc: alloc, 93 manager: netManager, 94 networkConfigurator: netConfigurator, 95 taskEnv: taskEnv, 96 logger: logger, 97 } 98 } 99 100 func (h *networkHook) Name() string { 101 return "network" 102 } 103 104 func (h *networkHook) Prerun() error { 105 tg := h.alloc.Job.LookupTaskGroup(h.alloc.TaskGroup) 106 if len(tg.Networks) == 0 || tg.Networks[0].Mode == "host" || tg.Networks[0].Mode == "" { 107 return nil 108 } 109 110 if h.manager == nil || h.networkConfigurator == nil { 111 h.logger.Trace("shared network namespaces are not supported on this platform, skipping network hook") 112 return nil 113 } 114 115 // Perform our networks block interpolation. 116 interpolatedNetworks := taskenv.InterpolateNetworks(h.taskEnv, tg.Networks) 117 118 // Interpolated values need to be validated. It is also possible a user 119 // supplied hostname avoids the validation on job registrations because it 120 // looks like it includes interpolation, when it doesn't. 121 if interpolatedNetworks[0].Hostname != "" { 122 if _, ok := dns.IsDomainName(interpolatedNetworks[0].Hostname); !ok { 123 return fmt.Errorf("network hostname %q is not a valid DNS name", interpolatedNetworks[0].Hostname) 124 } 125 } 126 127 // Our network create request. 128 networkCreateReq := drivers.NetworkCreateRequest{ 129 Hostname: interpolatedNetworks[0].Hostname, 130 } 131 132 spec, created, err := h.manager.CreateNetwork(h.alloc.ID, &networkCreateReq) 133 if err != nil { 134 return fmt.Errorf("failed to create network for alloc: %v", err) 135 } 136 137 if spec != nil { 138 h.spec = spec 139 h.isolationSetter.SetNetworkIsolation(spec) 140 } 141 142 if created { 143 status, err := h.networkConfigurator.Setup(context.TODO(), h.alloc, spec) 144 if err != nil { 145 return fmt.Errorf("failed to configure networking for alloc: %v", err) 146 } 147 148 // If the driver set the sandbox hostname label, then we will use that 149 // to set the HostsConfig.Hostname. Otherwise, identify the sandbox 150 // container ID which will have been used to set the network namespace 151 // hostname. 152 if hostname, ok := spec.Labels[dockerNetSpecHostnameKey]; ok { 153 h.spec.HostsConfig = &drivers.HostsConfig{ 154 Address: status.Address, 155 Hostname: hostname, 156 } 157 } else if hostname, ok := spec.Labels[dockerNetSpecLabelKey]; ok { 158 159 // the docker_sandbox_container_id is the full ID of the pause 160 // container, whereas we want the shortened name that dockerd sets 161 // as the pause container's hostname. 162 if len(hostname) > 12 { 163 hostname = hostname[:12] 164 } 165 166 h.spec.HostsConfig = &drivers.HostsConfig{ 167 Address: status.Address, 168 Hostname: hostname, 169 } 170 } 171 172 h.networkStatusSetter.SetNetworkStatus(status) 173 } 174 return nil 175 } 176 177 func (h *networkHook) Postrun() error { 178 if h.spec == nil { 179 return nil 180 } 181 182 if err := h.networkConfigurator.Teardown(context.TODO(), h.alloc, h.spec); err != nil { 183 h.logger.Error("failed to cleanup network for allocation, resources may have leaked", "alloc", h.alloc.ID, "error", err) 184 } 185 return h.manager.DestroyNetwork(h.alloc.ID, h.spec) 186 }