Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,13 @@ spec:
x-kubernetes-validations:
- message: empty tag keys or values aren't supported
rule: self.all(k, k != '' && self[k] != '')
vpcID:
description: |-
VPCID is the VPC ID to filter security groups by.
When specified with Name or Tags, only security groups in this VPC will be selected.
This is useful when multiple VPCs have security groups with the same name.
pattern: vpc-[0-9a-z]+
type: string
type: object
maxItems: 30
type: array
Expand Down
49 changes: 49 additions & 0 deletions examples/v1/security-group-with-vpc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: security-group-with-vpc
spec:
# Select security groups by name within a specific VPC
# This is useful when multiple VPCs have security groups with the same name
securityGroupSelectorTerms:
# Select 'k8s-node' security group only from vpc-12345678
- name: k8s-node
vpcID: vpc-12345678
# You can also use tags with VPC ID
- tags:
karpenter.sh/discovery: my-cluster
Environment: production
vpcID: vpc-12345678
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-cluster
amiSelectorTerms:
- alias: al2023@latest
role: "KarpenterNodeRole-my-cluster"
---
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: default
spec:
template:
spec:
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: security-group-with-vpc
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["c", "m", "r"]
limits:
cpu: 1000
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 1m
7 changes: 7 additions & 0 deletions pkg/apis/crds/karpenter.k8s.aws_ec2nodeclasses.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,13 @@ spec:
x-kubernetes-validations:
- message: empty tag keys or values aren't supported
rule: self.all(k, k != '' && self[k] != '')
vpcID:
description: |-
VPCID is the VPC ID to filter security groups by.
When specified with Name or Tags, only security groups in this VPC will be selected.
This is useful when multiple VPCs have security groups with the same name.
pattern: vpc-[0-9a-z]+
type: string
type: object
maxItems: 30
type: array
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/v1/ec2nodeclass.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ type SecurityGroupSelectorTerm struct {
// Name is the security group name in EC2.
// This value is the name field, which is different from the name tag.
Name string `json:"name,omitempty"`
// VPCID is the VPC ID to filter security groups by.
// When specified with Name or Tags, only security groups in this VPC will be selected.
// This is useful when multiple VPCs have security groups with the same name.
// +kubebuilder:validation:Pattern:="vpc-[0-9a-z]+"
// +optional
VPCID string `json:"vpcID,omitempty"`
}

type CapacityReservationSelectorTerm struct {
Expand Down
29 changes: 29 additions & 0 deletions pkg/apis/v1/ec2nodeclass_validation_cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,35 @@ var _ = Describe("CEL/Validation", func() {
}
Expect(env.Client.Create(ctx, nc)).ToNot(Succeed())
})
It("should succeed with a valid security group selector on name with vpcID", func() {
nc.Spec.SecurityGroupSelectorTerms = []v1.SecurityGroupSelectorTerm{
{
Name: "my-security-group",
VPCID: "vpc-12345678",
},
}
Expect(env.Client.Create(ctx, nc)).To(Succeed())
})
It("should succeed with a valid security group selector on tags with vpcID", func() {
nc.Spec.SecurityGroupSelectorTerms = []v1.SecurityGroupSelectorTerm{
{
Tags: map[string]string{
"test": "testvalue",
},
VPCID: "vpc-12345678",
},
}
Expect(env.Client.Create(ctx, nc)).To(Succeed())
})
It("should succeed with a valid security group selector on id with vpcID", func() {
nc.Spec.SecurityGroupSelectorTerms = []v1.SecurityGroupSelectorTerm{
{
ID: "sg-12345749",
VPCID: "vpc-12345678",
},
}
Expect(env.Client.Create(ctx, nc)).To(Succeed())
})
})
Context("CapacityReservationSelectorTerms", func() {
It("should succeed with a valid capacity reservation selector on tags", func() {
Expand Down
36 changes: 36 additions & 0 deletions pkg/controllers/nodeclass/securitygroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,40 @@ var _ = Describe("NodeClass Security Group Status Controller", func() {
Expect(nodeClass.Status.SecurityGroups).To(BeNil())
Expect(nodeClass.StatusConditions().Get(v1.ConditionTypeSecurityGroupsReady).IsFalse()).To(BeTrue())
})
It("Should resolve a valid selector for Security Groups by name with VPC ID", func() {
nodeClass.Spec.SecurityGroupSelectorTerms = []v1.SecurityGroupSelectorTerm{
{
Name: "securityGroup-test1",
VPCID: "vpc-test123",
},
}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(nodeClass.Status.SecurityGroups).To(Equal([]v1.SecurityGroup{
{
ID: "sg-test1",
Name: "securityGroup-test1",
},
}))
Expect(nodeClass.StatusConditions().Get(v1.ConditionTypeSecurityGroupsReady).IsTrue()).To(BeTrue())
})
It("Should resolve a valid selector for Security Groups by tags with VPC ID", func() {
nodeClass.Spec.SecurityGroupSelectorTerms = []v1.SecurityGroupSelectorTerm{
{
Tags: map[string]string{"Name": "test-security-group-1"},
VPCID: "vpc-test123",
},
}
ExpectApplied(ctx, env.Client, nodeClass)
ExpectObjectReconciled(ctx, env.Client, controller, nodeClass)
nodeClass = ExpectExists(ctx, env.Client, nodeClass)
Expect(nodeClass.Status.SecurityGroups).To(Equal([]v1.SecurityGroup{
{
ID: "sg-test1",
Name: "securityGroup-test1",
},
}))
Expect(nodeClass.StatusConditions().Get(v1.ConditionTypeSecurityGroupsReady).IsTrue()).To(BeTrue())
})
})
44 changes: 34 additions & 10 deletions pkg/providers/securitygroup/securitygroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,25 @@ func (p *DefaultProvider) getSecurityGroups(ctx context.Context, nodeClass *v1.E
}

func getFilterSets(terms []v1.SecurityGroupSelectorTerm) (res [][]ec2types.Filter) {
idFilter := ec2types.Filter{Name: aws.String("group-id")}
nameFilter := ec2types.Filter{Name: aws.String("group-name")}
nameFilters := make(map[string][]string) // VPC ID -> Names mapping
for _, term := range terms {
switch {
case term.ID != "":
idFilter.Values = append(idFilter.Values, term.ID)
// IDs are unique, but still respect VPC filter if specified
filters := []ec2types.Filter{{Name: aws.String("group-id"), Values: []string{term.ID}}}
if term.VPCID != "" {
filters = append(filters, ec2types.Filter{
Name: aws.String("vpc-id"),
Values: []string{term.VPCID},
})
}
res = append(res, filters)
case term.Name != "":
nameFilter.Values = append(nameFilter.Values, term.Name)
// Group names with VPC ID - create separate filter sets per VPC
vpcKey := term.VPCID // empty string for no VPC filter
nameFilters[vpcKey] = append(nameFilters[vpcKey], term.Name)
default:
// Tag-based selection
var filters []ec2types.Filter
for k, v := range term.Tags {
if v == "*" {
Expand All @@ -122,14 +132,28 @@ func getFilterSets(terms []v1.SecurityGroupSelectorTerm) (res [][]ec2types.Filte
})
}
}
res = append(res, filters)
// Add VPC filter if specified
if term.VPCID != "" {
filters = append(filters, ec2types.Filter{
Name: aws.String("vpc-id"),
Values: []string{term.VPCID},
})
}
if len(filters) > 0 {
res = append(res, filters)
}
}
}
if len(idFilter.Values) > 0 {
res = append(res, []ec2types.Filter{idFilter})
}
if len(nameFilter.Values) > 0 {
res = append(res, []ec2types.Filter{nameFilter})
// Add name filters grouped by VPC
for vpcID, names := range nameFilters {
filters := []ec2types.Filter{{Name: aws.String("group-name"), Values: names}}
if vpcID != "" {
filters = append(filters, ec2types.Filter{
Name: aws.String("vpc-id"),
Values: []string{vpcID},
})
}
res = append(res, filters)
}
return res
}
Loading