From 2a0793c155e64543e4ce9866f7d2ba56f15b747a Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Mon, 23 Mar 2015 16:34:35 -0700 Subject: [PATCH] Add HostNetworking container option to API. This allows a container to run within the same networking namespace as the host. This will be locked down by default using a flag on the master and nodes (similar to how privileged is handled today). --- pkg/api/types.go | 5 +++++ pkg/api/v1beta1/conversion.go | 2 ++ pkg/api/v1beta1/defaults.go | 17 +++++++++++++++++ pkg/api/v1beta1/defaults_test.go | 25 +++++++++++++++++++++++++ pkg/api/v1beta1/types.go | 9 +++++++++ pkg/api/v1beta2/conversion.go | 2 ++ pkg/api/v1beta2/defaults.go | 17 +++++++++++++++++ pkg/api/v1beta2/defaults_test.go | 25 +++++++++++++++++++++++++ pkg/api/v1beta2/types.go | 9 +++++++++ pkg/api/v1beta3/defaults.go | 14 ++++++++++++++ pkg/api/v1beta3/defaults_test.go | 26 ++++++++++++++++++++++++++ pkg/api/v1beta3/types.go | 5 +++++ pkg/api/validation/validation.go | 15 +++++++++++++++ pkg/api/validation/validation_test.go | 20 ++++++++++++++++++++ 14 files changed, 191 insertions(+) diff --git a/pkg/api/types.go b/pkg/api/types.go index f34a71e98b2..0db60e1702b 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -288,6 +288,7 @@ type ContainerPort struct { // in a pod must have a unique name. Name string `json:"name,omitempty"` // Optional: If specified, this must be a valid port number, 0 < x < 65536. + // If HostNetwork is specified, this must match ContainerPort. HostPort int `json:"hostPort,omitempty"` // Required: This must be a valid port number, 0 < x < 65536. ContainerPort int `json:"containerPort"` @@ -587,6 +588,10 @@ type PodSpec struct { // the the scheduler simply schedules this pod onto that host, assuming that it fits // resource requirements. Host string `json:"host,omitempty"` + // Uses the host's network namespace. If this option is set, the ports that will be + // used must be specified. + // Optional: Default to false. + HostNetwork bool `json:"hostNetwork,omitempty"` } // PodStatus represents information about the status of a pod. Status may trail the actual diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index 1ecceeacc39..237d93fad49 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -602,6 +602,7 @@ func init() { } out.DNSPolicy = DNSPolicy(in.DNSPolicy) out.Version = "v1beta2" + out.HostNetwork = in.HostNetwork return nil }, func(in *ContainerManifest, out *newer.PodSpec, s conversion.Scope) error { @@ -615,6 +616,7 @@ func init() { return err } out.DNSPolicy = newer.DNSPolicy(in.DNSPolicy) + out.HostNetwork = in.HostNetwork return nil }, diff --git a/pkg/api/v1beta1/defaults.go b/pkg/api/v1beta1/defaults.go index 95f63c22633..0c92ce1d496 100644 --- a/pkg/api/v1beta1/defaults.go +++ b/pkg/api/v1beta1/defaults.go @@ -69,11 +69,17 @@ func init() { if obj.DNSPolicy == "" { obj.DNSPolicy = DNSClusterFirst } + if obj.HostNetwork { + defaultHostNetworkPorts(&obj.Containers) + } }, func(obj *ContainerManifest) { if obj.DNSPolicy == "" { obj.DNSPolicy = DNSClusterFirst } + if obj.HostNetwork { + defaultHostNetworkPorts(&obj.Containers) + } }, func(obj *LivenessProbe) { if obj.TimeoutSeconds == 0 { @@ -102,3 +108,14 @@ func init() { }, ) } + +// With host networking default all host ports to container ports. +func defaultHostNetworkPorts(containers *[]Container) { + for i := range *containers { + for j := range (*containers)[i].Ports { + if (*containers)[i].Ports[j].HostPort == 0 { + (*containers)[i].Ports[j].HostPort = (*containers)[i].Ports[j].ContainerPort + } + } + } +} diff --git a/pkg/api/v1beta1/defaults_test.go b/pkg/api/v1beta1/defaults_test.go index a9464265406..3a85ab483b8 100644 --- a/pkg/api/v1beta1/defaults_test.go +++ b/pkg/api/v1beta1/defaults_test.go @@ -80,3 +80,28 @@ func TestSetDefaultNamespace(t *testing.T) { t.Errorf("Expected phase %v, got %v", current.NamespaceActive, s2.Status.Phase) } } + +func TestSetDefaultContainerManifestHostNetwork(t *testing.T) { + portNum := 8080 + s := current.ContainerManifest{} + s.HostNetwork = true + s.Containers = []current.Container{ + { + Ports: []current.ContainerPort{ + { + ContainerPort: portNum, + }, + }, + }, + } + obj2 := roundTrip(t, runtime.Object(¤t.ContainerManifestList{ + Items: []current.ContainerManifest{s}, + })) + sList2 := obj2.(*current.ContainerManifestList) + s2 := sList2.Items[0] + + hostPortNum := s2.Containers[0].Ports[0].HostPort + if hostPortNum != portNum { + t.Errorf("Expected container port to be defaulted, was made %d instead of %d", hostPortNum, portNum) + } +} diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index b3ad64b0471..3f04f267203 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -63,6 +63,10 @@ type ContainerManifest struct { RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" description:"restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"` // Optional: Set DNS policy. Defaults to "ClusterFirst" DNSPolicy DNSPolicy `json:"dnsPolicy,omitempty" description:"DNS policy for containers within the pod; one of 'ClusterFirst' or 'Default'"` + // Uses the host's network namespace. If this option is set, the ports that will be + // used must be specified. + // Optional: Default to false. + HostNetwork bool `json:"hostNetwork,omitempty" description:"host networking requested for this pod"` } // ContainerManifestList is used to communicate container manifests to kubelet. @@ -181,6 +185,7 @@ type ContainerPort struct { // in a pod must have a unique name. Name string `json:"name,omitempty" description:"name for the port that can be referred to by services; must be a DNS_LABEL and unique without the pod"` // Optional: If specified, this must be a valid port number, 0 < x < 65536. + // If HostNetwork is specified, this must match ContainerPort. HostPort int `json:"hostPort,omitempty" description:"number of port to expose on the host; most containers do not need this"` // Required: This must be a valid port number, 0 < x < 65536. ContainerPort int `json:"containerPort" description:"number of port to expose on the pod's IP address"` @@ -1087,6 +1092,10 @@ type PodSpec struct { // the the scheduler simply schedules this pod onto that host, assuming that it fits // resource requirements. Host string `json:"host,omitempty" description:"host requested for this pod"` + // Uses the host's network namespace. If this option is set, the ports that will be + // used must be specified. + // Optional: Default to false. + HostNetwork bool `json:"hostNetwork,omitempty" description:"host networking requested for this pod"` } // List holds a list of objects, which may not be known by the server. diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index 249b25a6d6f..182fdf2e471 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -462,6 +462,7 @@ func init() { } out.DNSPolicy = DNSPolicy(in.DNSPolicy) out.Version = "v1beta2" + out.HostNetwork = in.HostNetwork return nil }, func(in *ContainerManifest, out *newer.PodSpec, s conversion.Scope) error { @@ -475,6 +476,7 @@ func init() { return err } out.DNSPolicy = newer.DNSPolicy(in.DNSPolicy) + out.HostNetwork = in.HostNetwork return nil }, diff --git a/pkg/api/v1beta2/defaults.go b/pkg/api/v1beta2/defaults.go index 9c5cf60163a..bc72e4cc616 100644 --- a/pkg/api/v1beta2/defaults.go +++ b/pkg/api/v1beta2/defaults.go @@ -71,11 +71,17 @@ func init() { if obj.DNSPolicy == "" { obj.DNSPolicy = DNSClusterFirst } + if obj.HostNetwork { + defaultHostNetworkPorts(&obj.Containers) + } }, func(obj *ContainerManifest) { if obj.DNSPolicy == "" { obj.DNSPolicy = DNSClusterFirst } + if obj.HostNetwork { + defaultHostNetworkPorts(&obj.Containers) + } }, func(obj *LivenessProbe) { if obj.TimeoutSeconds == 0 { @@ -104,3 +110,14 @@ func init() { }, ) } + +// With host networking default all container ports to host ports. +func defaultHostNetworkPorts(containers *[]Container) { + for i := range *containers { + for j := range (*containers)[i].Ports { + if (*containers)[i].Ports[j].HostPort == 0 { + (*containers)[i].Ports[j].HostPort = (*containers)[i].Ports[j].ContainerPort + } + } + } +} diff --git a/pkg/api/v1beta2/defaults_test.go b/pkg/api/v1beta2/defaults_test.go index 0649462c4e9..a038dce069a 100644 --- a/pkg/api/v1beta2/defaults_test.go +++ b/pkg/api/v1beta2/defaults_test.go @@ -80,3 +80,28 @@ func TestSetDefaultNamespace(t *testing.T) { t.Errorf("Expected phase %v, got %v", current.NamespaceActive, s2.Status.Phase) } } + +func TestSetDefaultContainerManifestHostNetwork(t *testing.T) { + portNum := 8080 + s := current.ContainerManifest{} + s.HostNetwork = true + s.Containers = []current.Container{ + { + Ports: []current.ContainerPort{ + { + ContainerPort: portNum, + }, + }, + }, + } + obj2 := roundTrip(t, runtime.Object(¤t.ContainerManifestList{ + Items: []current.ContainerManifest{s}, + })) + sList2 := obj2.(*current.ContainerManifestList) + s2 := sList2.Items[0] + + hostPortNum := s2.Containers[0].Ports[0].HostPort + if hostPortNum != portNum { + t.Errorf("Expected container port to be defaulted, was made %d instead of %d", hostPortNum, portNum) + } +} diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index f7815297efc..c9976d2d519 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -130,6 +130,7 @@ type ContainerPort struct { // in a pod must have a unique name. Name string `json:"name,omitempty" description:"name for the port that can be referred to by services; must be a DNS_LABEL and unique without the pod"` // Optional: If specified, this must be a valid port number, 0 < x < 65536. + // If HostNetwork is specified, this must match ContainerPort. HostPort int `json:"hostPort,omitempty" description:"number of port to expose on the host; most containers do not need this"` // Required: This must be a valid port number, 0 < x < 65536. ContainerPort int `json:"containerPort" description:"number of port to expose on the pod's IP address"` @@ -1109,6 +1110,10 @@ type ContainerManifest struct { RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" description:"restart policy for all containers within the pod; one of RestartPolicyAlways, RestartPolicyOnFailure, RestartPolicyNever"` // Optional: Set DNS policy. Defaults to "ClusterFirst" DNSPolicy DNSPolicy `json:"dnsPolicy,omitempty" description:"DNS policy for containers within the pod; one of 'ClusterFirst' or 'Default'"` + // Uses the host's network namespace. If this option is set, the ports that will be + // used must be specified. + // Optional: Default to false. + HostNetwork bool `json:"hostNetwork,omitempty" description:"host networking requested for this pod"` } // ContainerManifestList is used to communicate container manifests to kubelet. @@ -1149,6 +1154,10 @@ type PodSpec struct { // the the scheduler simply schedules this pod onto that host, assuming that it fits // resource requirements. Host string `json:"host,omitempty" description:"host requested for this pod"` + // Uses the host's network namespace. If this option is set, the ports that will be + // used must be specified. + // Optional: Default to false. + HostNetwork bool `json:"hostNetwork,omitempty" description:"host networking requested for this pod"` } // List holds a list of objects, which may not be known by the server. diff --git a/pkg/api/v1beta3/defaults.go b/pkg/api/v1beta3/defaults.go index 0045489ae22..55b4f2afea4 100644 --- a/pkg/api/v1beta3/defaults.go +++ b/pkg/api/v1beta3/defaults.go @@ -67,6 +67,9 @@ func init() { if obj.RestartPolicy == "" { obj.RestartPolicy = RestartPolicyAlways } + if obj.HostNetwork { + defaultHostNetworkPorts(&obj.Containers) + } }, func(obj *Probe) { if obj.TimeoutSeconds == 0 { @@ -101,3 +104,14 @@ func init() { }, ) } + +// With host networking default all container ports to host ports. +func defaultHostNetworkPorts(containers *[]Container) { + for i := range *containers { + for j := range (*containers)[i].Ports { + if (*containers)[i].Ports[j].HostPort == 0 { + (*containers)[i].Ports[j].HostPort = (*containers)[i].Ports[j].ContainerPort + } + } + } +} diff --git a/pkg/api/v1beta3/defaults_test.go b/pkg/api/v1beta3/defaults_test.go index 84e51d30485..62527a99a6c 100644 --- a/pkg/api/v1beta3/defaults_test.go +++ b/pkg/api/v1beta3/defaults_test.go @@ -97,3 +97,29 @@ func TestSetDefaultNamespace(t *testing.T) { t.Errorf("Expected phase %v, got %v", current.NamespaceActive, s2.Status.Phase) } } + +func TestSetDefaultPodSpecHostNetwork(t *testing.T) { + portNum := 8080 + s := current.PodSpec{} + s.HostNetwork = true + s.Containers = []current.Container{ + { + Ports: []current.ContainerPort{ + { + ContainerPort: portNum, + }, + }, + }, + } + pod := ¤t.Pod{ + Spec: s, + } + obj2 := roundTrip(t, runtime.Object(pod)) + pod2 := obj2.(*current.Pod) + s2 := pod2.Spec + + hostPortNum := s2.Containers[0].Ports[0].HostPort + if hostPortNum != portNum { + t.Errorf("Expected container port to be defaulted, was made %d instead of %d", hostPortNum, portNum) + } +} diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 15e369104af..511093b2248 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -298,6 +298,7 @@ type ContainerPort struct { // in a pod must have a unique name. Name string `json:"name,omitempty" description:"name for the port that can be referred to by services; must be a DNS_LABEL and unique without the pod"` // Optional: If specified, this must be a valid port number, 0 < x < 65536. + // If HostNetwork is specified, this must match ContainerPort. HostPort int `json:"hostPort,omitempty" description:"number of port to expose on the host; most containers do not need this"` // Required: This must be a valid port number, 0 < x < 65536. ContainerPort int `json:"containerPort" description:"number of port to expose on the pod's IP address"` @@ -586,6 +587,10 @@ type PodSpec struct { // the the scheduler simply schedules this pod onto that host, assuming that it fits // resource requirements. Host string `json:"host,omitempty" description:"host requested for this pod"` + // Uses the host's network namespace. If this option is set, the ports that will be + // used must be specified. + // Optional: Default to false. + HostNetwork bool `json:"hostNetwork,omitempty" description:"host networking requested for this pod"` } // PodStatus represents information about the status of a pod. Status may trail the actual diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index f85b398cc4f..da09fd88b2b 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -650,6 +650,20 @@ func validateDNSPolicy(dnsPolicy *api.DNSPolicy) errs.ValidationErrorList { return allErrors } +func validateHostNetwork(hostNetwork bool, containers []api.Container) errs.ValidationErrorList { + allErrors := errs.ValidationErrorList{} + if hostNetwork { + for _, container := range containers { + for _, port := range container.Ports { + if port.HostPort != port.ContainerPort { + allErrors = append(allErrors, errs.NewFieldInvalid("containerPort", port.ContainerPort, "containerPort must match hostPort if hostNetwork is set to true")) + } + } + } + } + return allErrors +} + // ValidatePod tests if required fields in the pod are set. func ValidatePod(pod *api.Pod) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} @@ -672,6 +686,7 @@ func ValidatePodSpec(spec *api.PodSpec) errs.ValidationErrorList { allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy).Prefix("restartPolicy")...) allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy).Prefix("dnsPolicy")...) allErrs = append(allErrs, ValidateLabels(spec.NodeSelector, "nodeSelector")...) + allErrs = append(allErrs, validateHostNetwork(spec.HostNetwork, spec.Containers).Prefix("hostNetwork")...) return allErrs } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index c656c548a87..325e29ec4ff 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -712,6 +712,16 @@ func TestValidatePodSpec(t *testing.T) { Host: "foobar", DNSPolicy: api.DNSClusterFirst, }, + { // Populate HostNetwork. + Containers: []api.Container{ + {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []api.ContainerPort{ + {HostPort: 8080, ContainerPort: 8080, Protocol: "TCP"}}, + }, + }, + HostNetwork: true, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, } for i := range successCases { if errs := ValidatePodSpec(&successCases[i]); len(errs) != 0 { @@ -745,6 +755,16 @@ func TestValidatePodSpec(t *testing.T) { DNSPolicy: api.DNSClusterFirst, Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}}, }, + "with hostNetwork hostPort not equal to containerPort": { + Containers: []api.Container{ + {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", Ports: []api.ContainerPort{ + {HostPort: 8080, ContainerPort: 2600, Protocol: "TCP"}}, + }, + }, + HostNetwork: true, + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + }, } for k, v := range failureCases { if errs := ValidatePodSpec(&v); len(errs) == 0 {