Skip to content

Commit

Permalink
Merge pull request kubernetes#10974 from brendandburns/lakesong
Browse files Browse the repository at this point in the history
Automatically open a firewall when creating a GCE load balancer.
  • Loading branch information
rjnagal committed Jul 11, 2015
2 parents 6e30302 + a8f02e5 commit d61d272
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 12 deletions.
8 changes: 8 additions & 0 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,14 @@ func ValidateService(service *api.Service) errs.ValidationErrorList {
if len(service.Spec.Ports) == 0 {
allErrs = append(allErrs, errs.NewFieldRequired("spec.ports"))
}
if service.Spec.Type == api.ServiceTypeLoadBalancer {
for ix := range service.Spec.Ports {
port := &service.Spec.Ports[ix]
if port.Port == 10250 {
allErrs = append(allErrs, errs.NewFieldInvalid(fmt.Sprintf("spec.ports[%d].port", ix), port.Port, "can not expose port 10250 externally since it is used by kubelet"))
}
}
}
allPortNames := util.StringSet{}
for i := range service.Spec.Ports {
allErrs = append(allErrs, validateServicePort(&service.Spec.Ports[i], len(service.Spec.Ports) > 1, &allPortNames).PrefixIndex(i).Prefix("spec.ports")...)
Expand Down
10 changes: 10 additions & 0 deletions pkg/api/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,16 @@ func TestValidateService(t *testing.T) {
},
numErrs: 0,
},
{
// For now we open firewalls, and its insecure if we open 10250, remove this
// when we have better protections in place.
name: "invalid port type=LoadBalancer",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "kubelet", Port: 10250, Protocol: "TCP", TargetPort: util.NewIntOrStringFromInt(12345)})
},
numErrs: 1,
},
}

for _, tc := range testCases {
Expand Down
56 changes: 55 additions & 1 deletion pkg/cloudprovider/gce/gce.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ func translateAffinityType(affinityType api.ServiceAffinity) GCEAffinityType {
}
}

func makeFirewallName(name string) string {
return fmt.Sprintf("k8s-fw-%s", name)
}

// CreateTCPLoadBalancer is an implementation of TCPLoadBalancer.CreateTCPLoadBalancer.
// TODO(a-robinson): Don't just ignore specified IP addresses. Check if they're
// owned by the project and available to be used, and use them if they are.
Expand Down Expand Up @@ -379,11 +383,44 @@ func (gce *GCECloud) CreateTCPLoadBalancer(name, region string, externalIP net.I
return nil, err
}

allowedPorts := make([]string, len(ports))
for ix := range ports {
allowedPorts[ix] = strconv.Itoa(ports[ix].Port)
}

hostTag := gce.computeHostTag(hosts[0])

firewall := &compute.Firewall{
Name: makeFirewallName(name),
Description: fmt.Sprintf("KubernetesAutoGenerated_OnlyAllowTrafficForDestinationIP_%s", fwd.IPAddress),
Network: gce.gceNetworkName(),
SourceRanges: []string{"0.0.0.0/0"},
TargetTags: []string{hostTag},
Allowed: []*compute.FirewallAllowed{
{
IPProtocol: "tcp",
Ports: allowedPorts,
},
},
}
if op, err = gce.service.Firewalls.Insert(gce.projectID, firewall).Do(); err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
return nil, err
}
if err = gce.waitForGlobalOp(op); err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
return nil, err
}

status := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: fwd.IPAddress}}
return status, nil
}

// This is kind of hacky, but the managed instance group adds 4 random chars and a hyphen
// to the base name.
func (gce *GCECloud) computeHostTag(host string) string {
return host[:len(host)-5]
}

// UpdateTCPLoadBalancer is an implementation of TCPLoadBalancer.UpdateTCPLoadBalancer.
func (gce *GCECloud) UpdateTCPLoadBalancer(name, region string, hosts []string) error {
pool, err := gce.service.TargetPools.Get(gce.projectID, region, name).Do()
Expand Down Expand Up @@ -456,6 +493,19 @@ func (gce *GCECloud) EnsureTCPLoadBalancerDeleted(name, region string) error {
if err != nil {
glog.Warningf("Failed waiting for Target Pool %s to be deleted: got error %s.", name, err.Error())
}
fwName := makeFirewallName(name)
op, err = gce.service.Firewalls.Delete(gce.projectID, fwName).Do()
if err != nil && isHTTPErrorCode(err, http.StatusNotFound) {
glog.Infof("Firewall doesn't exist, moving on to deleting target pool.")
} else if err != nil {
glog.Warningf("Failed to delete firewall %s, got error %v", fwName, err)
return err
} else {
if err = gce.waitForGlobalOp(op); err != nil {
glog.Warningf("Failed waiting for Firewall %s to be deleted. Got error: %v", fwName, err)
return err
}
}
return err
}

Expand Down Expand Up @@ -670,6 +720,10 @@ func (gce *GCECloud) ListRoutes(clusterName string) ([]*cloudprovider.Route, err
return routes, nil
}

func (gce *GCECloud) gceNetworkName() string {
return fmt.Sprintf("global/networks/%s", gce.networkName)
}

func (gce *GCECloud) CreateRoute(clusterName string, nameHint string, route *cloudprovider.Route) error {
routeName := truncateClusterName(clusterName) + "-" + nameHint

Expand All @@ -678,7 +732,7 @@ func (gce *GCECloud) CreateRoute(clusterName string, nameHint string, route *clo
Name: routeName,
DestRange: route.DestinationCIDR,
NextHopInstance: fmt.Sprintf("zones/%s/instances/%s", gce.zone, instanceName),
Network: fmt.Sprintf("global/networks/%s", gce.networkName),
Network: gce.gceNetworkName(),
Priority: 1000,
Description: k8sNodeRouteTag,
}).Do()
Expand Down
24 changes: 24 additions & 0 deletions pkg/cloudprovider/gce/gce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,27 @@ func TestGetRegion(t *testing.T) {
t.Errorf("Unexpected region: %s", zone.Region)
}
}

func TestGetHostTag(t *testing.T) {
tests := []struct {
host string
expected string
}{
{
host: "kubernetes-minion-559o",
expected: "kubernetes-minion",
},
{
host: "gke-test-ea6e8c80-node-8ytk",
expected: "gke-test-ea6e8c80-node",
},
}

gce := &GCECloud{}
for _, test := range tests {
hostTag := gce.computeHostTag(test.host)
if hostTag != test.expected {
t.Errorf("expected: %s, saw: %s for %s", test.expected, hostTag, test.host)
}
}
}
10 changes: 0 additions & 10 deletions pkg/kubectl/cmd/expose.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,5 @@ func RunExpose(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
}
}

if cmdutil.GetFlagBool(cmd, "create-external-load-balancer") {
msg := fmt.Sprintf(`
An external load-balanced service was created. On many platforms (e.g. Google Compute Engine),
you will also need to explicitly open a firewall rule for the service port (%d) to serve traffic.
See https://github.com/GoogleCloudPlatform/kubernetes/tree/master/docs/services-firewall.md for more details.
`, cmdutil.GetFlagInt(cmd, "port"))
out.Write([]byte(msg))
}

return f.PrintObject(cmd, object, out)
}
6 changes: 5 additions & 1 deletion test/e2e/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,12 @@ var _ = Describe("Services", func() {
}
}()

inboundPort := 3000

service := t.BuildServiceSpec()
service.Spec.Type = api.ServiceTypeLoadBalancer
service.Spec.Ports[0].Port = inboundPort
service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(80)

By("creating service " + serviceName + " with external load balancer in namespace " + ns)
result, err := t.CreateService(service)
Expand Down Expand Up @@ -278,7 +282,7 @@ var _ = Describe("Services", func() {
testReachable(pickMinionIP(c), port.NodePort)

By("hitting the pod through the service's external load balancer")
testLoadBalancerReachable(ingress, 80)
testLoadBalancerReachable(ingress, inboundPort)
})

It("should be able to create a functioning NodePort service", func() {
Expand Down

0 comments on commit d61d272

Please sign in to comment.