Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow additional SGs to be added to API loadbalancer #4036

Merged
12 changes: 12 additions & 0 deletions docs/cluster_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ spec:
When configuring a LoadBalancer, you can also choose to have a public ELB or an internal (VPC only) ELB. The `type`
field should be `Public` or `Internal`.

Also, you can add precreated additional security groups to the load balancer by setting `additionalSecurityGroups`.

```yaml
spec:
api:
loadBalancer:
type: Public
additionalSecurityGroups:
- sg-xxxxxxxx
- sg-xxxxxxxx
```

Additionally, you can increase idle timeout of the load balancer by setting its `idleTimeoutSeconds`. The default idle timeout is 5 minutes, with a maximum of 3600 seconds (60 minutes) being allowed by AWS.
For more information see [configuring idle timeouts](http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/config-idle-timeout.html).

Expand Down
6 changes: 4 additions & 2 deletions pkg/apis/kops/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,11 @@ const (
LoadBalancerTypeInternal LoadBalancerType = "Internal"
)

// LoadBalancerAccessSpec provides configuration details related to API LoadBalancer and its access
type LoadBalancerAccessSpec struct {
Type LoadBalancerType `json:"type,omitempty"`
IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"`
Type LoadBalancerType `json:"type,omitempty"`
IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"`
AdditionalSecurityGroups []string `json:"additionalSecurityGroups,omitempty"`
}

// KubeDNSConfig defines the kube dns configuration
Expand Down
6 changes: 4 additions & 2 deletions pkg/apis/kops/v1alpha1/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,11 @@ const (
LoadBalancerTypeInternal LoadBalancerType = "Internal"
)

// LoadBalancerAccessSpec provides configuration details related to API LoadBalancer and its access
type LoadBalancerAccessSpec struct {
Type LoadBalancerType `json:"type,omitempty"`
IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"`
Type LoadBalancerType `json:"type,omitempty"`
IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"`
AdditionalSecurityGroups []string `json:"additionalSecurityGroups,omitempty"`
}

// KubeDNSConfig defines the kube dns configuration
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/kops/v1alpha1/zz_generated.conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2281,6 +2281,7 @@ func Convert_kops_LeaderElectionConfiguration_To_v1alpha1_LeaderElectionConfigur
func autoConvert_v1alpha1_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec(in *LoadBalancerAccessSpec, out *kops.LoadBalancerAccessSpec, s conversion.Scope) error {
out.Type = kops.LoadBalancerType(in.Type)
out.IdleTimeoutSeconds = in.IdleTimeoutSeconds
out.AdditionalSecurityGroups = in.AdditionalSecurityGroups
return nil
}

Expand All @@ -2292,6 +2293,7 @@ func Convert_v1alpha1_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec(in *
func autoConvert_kops_LoadBalancerAccessSpec_To_v1alpha1_LoadBalancerAccessSpec(in *kops.LoadBalancerAccessSpec, out *LoadBalancerAccessSpec, s conversion.Scope) error {
out.Type = LoadBalancerType(in.Type)
out.IdleTimeoutSeconds = in.IdleTimeoutSeconds
out.AdditionalSecurityGroups = in.AdditionalSecurityGroups
return nil
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/kops/v1alpha1/zz_generated.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2616,6 +2616,11 @@ func (in *LoadBalancerAccessSpec) DeepCopyInto(out *LoadBalancerAccessSpec) {
**out = **in
}
}
if in.AdditionalSecurityGroups != nil {
in, out := &in.AdditionalSecurityGroups, &out.AdditionalSecurityGroups
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/apis/kops/v1alpha2/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,11 @@ const (
LoadBalancerTypeInternal LoadBalancerType = "Internal"
)

// LoadBalancerAccessSpec provides configuration details related to API LoadBalancer and its access
type LoadBalancerAccessSpec struct {
Type LoadBalancerType `json:"type,omitempty"`
IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"`
Type LoadBalancerType `json:"type,omitempty"`
IdleTimeoutSeconds *int64 `json:"idleTimeoutSeconds,omitempty"`
AdditionalSecurityGroups []string `json:"additionalSecurityGroups,omitempty"`
}

type KubeDNSConfig struct {
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/kops/v1alpha2/zz_generated.conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2543,6 +2543,7 @@ func Convert_kops_LeaderElectionConfiguration_To_v1alpha2_LeaderElectionConfigur
func autoConvert_v1alpha2_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec(in *LoadBalancerAccessSpec, out *kops.LoadBalancerAccessSpec, s conversion.Scope) error {
out.Type = kops.LoadBalancerType(in.Type)
out.IdleTimeoutSeconds = in.IdleTimeoutSeconds
out.AdditionalSecurityGroups = in.AdditionalSecurityGroups
return nil
}

Expand All @@ -2554,6 +2555,7 @@ func Convert_v1alpha2_LoadBalancerAccessSpec_To_kops_LoadBalancerAccessSpec(in *
func autoConvert_kops_LoadBalancerAccessSpec_To_v1alpha2_LoadBalancerAccessSpec(in *kops.LoadBalancerAccessSpec, out *LoadBalancerAccessSpec, s conversion.Scope) error {
out.Type = LoadBalancerType(in.Type)
out.IdleTimeoutSeconds = in.IdleTimeoutSeconds
out.AdditionalSecurityGroups = in.AdditionalSecurityGroups
return nil
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2742,6 +2742,11 @@ func (in *LoadBalancerAccessSpec) DeepCopyInto(out *LoadBalancerAccessSpec) {
**out = **in
}
}
if in.AdditionalSecurityGroups != nil {
in, out := &in.AdditionalSecurityGroups, &out.AdditionalSecurityGroups
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

Expand Down
16 changes: 15 additions & 1 deletion pkg/apis/kops/validation/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ package validation
import (
"strings"

"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)

func awsValidateCluster(c *kops.Cluster) field.ErrorList {
return nil
allErrs := field.ErrorList{}

if c.Spec.API != nil {
if c.Spec.API.LoadBalancer != nil {
allErrs = append(allErrs, awsValidateAdditionalSecurityGroups(field.NewPath("spec", "api", "loadBalancer", "additionalSecurityGroups"), c.Spec.API.LoadBalancer.AdditionalSecurityGroups)...)
}
}

return allErrs
}

func awsValidateInstanceGroup(ig *kops.InstanceGroup) field.ErrorList {
Expand All @@ -41,7 +50,12 @@ func awsValidateInstanceGroup(ig *kops.InstanceGroup) field.ErrorList {
func awsValidateAdditionalSecurityGroups(fieldPath *field.Path, groups []string) field.ErrorList {
allErrs := field.ErrorList{}

names := sets.NewString()
for i, s := range groups {
if names.Has(s) {
allErrs = append(allErrs, field.Invalid(fieldPath.Index(i), s, "security groups with duplicate name found"))
}
names.Insert(s)
if strings.TrimSpace(s) == "" {
allErrs = append(allErrs, field.Invalid(fieldPath.Index(i), s, "security group cannot be empty, if specified"))
continue
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/kops/zz_generated.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2977,6 +2977,11 @@ func (in *LoadBalancerAccessSpec) DeepCopyInto(out *LoadBalancerAccessSpec) {
**out = **in
}
}
if in.AdditionalSecurityGroups != nil {
in, out := &in.AdditionalSecurityGroups, &out.AdditionalSecurityGroups
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/model/awsmodel/api_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,21 @@ func (b *APILoadBalancerBuilder) Build(c *fi.ModelBuilderContext) error {
}
}

// Add precreated additional security groups to the ELB
{
for _, id := range b.Cluster.Spec.API.LoadBalancer.AdditionalSecurityGroups {
t := &awstasks.SecurityGroup{
Name: fi.String(id),
ID: fi.String(id),
Shared: fi.Bool(true),
}
if err := c.EnsureTask(t); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The should be add task not ensure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chrislovecnm The reason for EnsureTask is the iteration over the list of security groups. If you added more security groups later and kept the old one, the iteration will be on the whole list and the will raise error for already added security groups if add task is used. EnsureTask will ignore already added security groups or tasks.

It is used also used in autoscalinggroup https://github.com/kubernetes/kops/blob/master/pkg/model/awsmodel/autoscalinggroup.go#L97-L107

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand now. Excellent

return err
}
elb.SecurityGroups = append(elb.SecurityGroups, t)
}
}

// Allow HTTPS to the master instances from the ELB
{
t := &awstasks.SecurityGroupRule{
Expand Down
8 changes: 6 additions & 2 deletions tests/integration/update_cluster/complex/in-v1alpha2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ metadata:
creationTimestamp: "2016-12-10T22:42:27Z"
name: complex.example.com
spec:
api:
loadBalancer:
type: Public
additionalSecurityGroups:
- sg-exampleid3
- sg-exampleid4
kubernetesApiAccess:
- 0.0.0.0/0
channel: stable
Expand Down Expand Up @@ -84,5 +90,3 @@ spec:
role: Master
subnets:
- us-test-1a


80 changes: 78 additions & 2 deletions tests/integration/update_cluster/complex/kubernetes.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ provider "aws" {
region = "us-test-1"
}

resource "aws_autoscaling_attachment" "master-us-test-1a-masters-complex-example-com" {
elb = "${aws_elb.api-complex-example-com.id}"
autoscaling_group_name = "${aws_autoscaling_group.master-us-test-1a-masters-complex-example-com.id}"
}

resource "aws_autoscaling_group" "master-us-test-1a-masters-complex-example-com" {
name = "master-us-test-1a.masters.complex.example.com"
launch_configuration = "${aws_launch_configuration.master-us-test-1a-masters-complex-example-com.id}"
Expand Down Expand Up @@ -150,6 +155,35 @@ resource "aws_ebs_volume" "us-test-1a-etcd-main-complex-example-com" {
}
}

resource "aws_elb" "api-complex-example-com" {
name = "api-complex-example-com-vd3t5n"

listener = {
instance_port = 443
instance_protocol = "TCP"
lb_port = 443
lb_protocol = "TCP"
}

security_groups = ["${aws_security_group.api-elb-complex-example-com.id}", "sg-exampleid3", "sg-exampleid4"]
subnets = ["${aws_subnet.us-test-1a-complex-example-com.id}"]

health_check = {
target = "SSL:443"
healthy_threshold = 2
unhealthy_threshold = 2
interval = 10
timeout = 5
}

idle_timeout = 300

tags = {
KubernetesCluster = "complex.example.com"
Name = "api.complex.example.com"
}
}

resource "aws_iam_instance_profile" "masters-complex-example-com" {
name = "masters.complex.example.com"
role = "${aws_iam_role.masters-complex-example-com.name}"
Expand Down Expand Up @@ -249,6 +283,19 @@ resource "aws_route" "0-0-0-0--0" {
gateway_id = "${aws_internet_gateway.complex-example-com.id}"
}

resource "aws_route53_record" "api-complex-example-com" {
name = "api.complex.example.com"
type = "A"

alias = {
name = "${aws_elb.api-complex-example-com.dns_name}"
zone_id = "${aws_elb.api-complex-example-com.zone_id}"
evaluate_target_health = false
}

zone_id = "/hostedzone/Z1AFAKE1ZON3YO"
}

resource "aws_route_table" "complex-example-com" {
vpc_id = "${aws_vpc.complex-example-com.id}"

Expand All @@ -263,6 +310,17 @@ resource "aws_route_table_association" "us-test-1a-complex-example-com" {
route_table_id = "${aws_route_table.complex-example-com.id}"
}

resource "aws_security_group" "api-elb-complex-example-com" {
name = "api-elb.complex.example.com"
vpc_id = "${aws_vpc.complex-example-com.id}"
description = "Security group for api ELB"

tags = {
KubernetesCluster = "complex.example.com"
Name = "api-elb.complex.example.com"
}
}

resource "aws_security_group" "masters-complex-example-com" {
name = "masters.complex.example.com"
vpc_id = "${aws_vpc.complex-example-com.id}"
Expand Down Expand Up @@ -312,15 +370,33 @@ resource "aws_security_group_rule" "all-node-to-node" {
protocol = "-1"
}

resource "aws_security_group_rule" "https-external-to-master-0-0-0-0--0" {
resource "aws_security_group_rule" "api-elb-egress" {
type = "egress"
security_group_id = "${aws_security_group.api-elb-complex-example-com.id}"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "https-api-elb-0-0-0-0--0" {
type = "ingress"
security_group_id = "${aws_security_group.masters-complex-example-com.id}"
security_group_id = "${aws_security_group.api-elb-complex-example-com.id}"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "https-elb-to-master" {
type = "ingress"
security_group_id = "${aws_security_group.masters-complex-example-com.id}"
source_security_group_id = "${aws_security_group.api-elb-complex-example-com.id}"
from_port = 443
to_port = 443
protocol = "tcp"
}

resource "aws_security_group_rule" "master-egress" {
type = "egress"
security_group_id = "${aws_security_group.masters-complex-example-com.id}"
Expand Down