Skip to content

Commit 1078bea

Browse files
authored
feat(eks): attach cluster security group to self-managed nodes (#12042)
Attaching the EKS managed cluster security group to self managed nodes to allow free traffic flow between managed and self-managed nodes. Closes #10884 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent bc5b9b6 commit 1078bea

File tree

5 files changed

+171
-7
lines changed

5 files changed

+171
-7
lines changed

packages/@aws-cdk/aws-eks/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,19 @@ cluster.addAutoScalingGroupCapacity('frontend-nodes', {
304304
});
305305
```
306306

307+
To connect an already initialized auto-scaling group, use the `cluster.connectAutoScalingGroupCapacity()` method:
308+
309+
```ts
310+
const asg = new ec2.AutoScalingGroup(...);
311+
cluster.connectAutoScalingGroupCapacity(asg);
312+
```
313+
314+
In both cases, the [cluster security group](https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html#cluster-sg) will be autoamtically attached to
315+
the auto-scaling group, allowing for traffic to flow freely between managed and self-managed nodes.
316+
317+
> **Note:** The default `updateType` for auto-scaling groups does not replace existing nodes. Since security groups are determined at launch time, self-managed nodes that were provisioned with version `1.78.0` or lower, will not be updated.
318+
> To apply the new configuration on all your self-managed nodes, you'll need to replace the nodes using the `UpdateType.REPLACING_UPDATE` policy for the [`updateType`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-autoscaling.AutoScalingGroup.html#updatetypespan-classapi-icon-api-icon-deprecated-titlethis-api-element-is-deprecated-its-use-is-not-recommended%EF%B8%8Fspan) property.
319+
307320
You can customize the [/etc/eks/boostrap.sh](https://github.com/awslabs/amazon-eks-ami/blob/master/files/bootstrap.sh) script, which is responsible
308321
for bootstrapping the node to the EKS cluster. For example, you can use `kubeletExtraArgs` to add custom node labels or taints.
309322

packages/@aws-cdk/aws-eks/lib/cluster.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,17 @@ export interface ICluster extends IResource, ec2.IConnectable {
6767
readonly clusterCertificateAuthorityData: string;
6868

6969
/**
70-
* The cluster security group that was created by Amazon EKS for the cluster.
70+
* The id of the cluster security group that was created by Amazon EKS for the cluster.
7171
* @attribute
7272
*/
7373
readonly clusterSecurityGroupId: string;
7474

75+
/**
76+
* The cluster security group that was created by Amazon EKS for the cluster.
77+
* @attribute
78+
*/
79+
readonly clusterSecurityGroup: ec2.ISecurityGroup;
80+
7581
/**
7682
* Amazon Resource Name (ARN) or alias of the customer master key (CMK).
7783
* @attribute
@@ -671,6 +677,7 @@ abstract class ClusterBase extends Resource implements ICluster {
671677
public abstract readonly clusterEndpoint: string;
672678
public abstract readonly clusterCertificateAuthorityData: string;
673679
public abstract readonly clusterSecurityGroupId: string;
680+
public abstract readonly clusterSecurityGroup: ec2.ISecurityGroup;
674681
public abstract readonly clusterEncryptionConfigKeyArn: string;
675682
public abstract readonly kubectlRole?: iam.IRole;
676683
public abstract readonly kubectlEnvironment?: { [key: string]: string };
@@ -802,10 +809,15 @@ export class Cluster extends ClusterBase {
802809
public readonly clusterCertificateAuthorityData: string;
803810

804811
/**
805-
* The cluster security group that was created by Amazon EKS for the cluster.
812+
* The id of the cluster security group that was created by Amazon EKS for the cluster.
806813
*/
807814
public readonly clusterSecurityGroupId: string;
808815

816+
/**
817+
* The cluster security group that was created by Amazon EKS for the cluster.
818+
*/
819+
public readonly clusterSecurityGroup: ec2.ISecurityGroup;
820+
809821
/**
810822
* Amazon Resource Name (ARN) or alias of the customer master key (CMK).
811823
*/
@@ -1070,16 +1082,16 @@ export class Cluster extends ClusterBase {
10701082
this.clusterSecurityGroupId = resource.attrClusterSecurityGroupId;
10711083
this.clusterEncryptionConfigKeyArn = resource.attrEncryptionConfigKeyArn;
10721084

1073-
const clusterSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(this, 'ClusterSecurityGroup', this.clusterSecurityGroupId);
1085+
this.clusterSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(this, 'ClusterSecurityGroup', this.clusterSecurityGroupId);
10741086

10751087
this.connections = new ec2.Connections({
1076-
securityGroups: [clusterSecurityGroup, securityGroup],
1088+
securityGroups: [this.clusterSecurityGroup, securityGroup],
10771089
defaultPort: ec2.Port.tcp(443), // Control Plane has an HTTPS API
10781090
});
10791091

10801092
// we can use the cluster security group since its already attached to the cluster
10811093
// and configured to allow connections from itself.
1082-
this.kubectlSecurityGroup = clusterSecurityGroup;
1094+
this.kubectlSecurityGroup = this.clusterSecurityGroup;
10831095

10841096
// use the cluster creation role to issue kubectl commands against the cluster because when the
10851097
// cluster is first created, that's the only role that has "system:masters" permissions
@@ -1254,6 +1266,9 @@ export class Cluster extends ClusterBase {
12541266
autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allUdp());
12551267
autoScalingGroup.connections.allowToAnyIpv4(ec2.Port.allIcmp());
12561268

1269+
// allow traffic to/from managed node groups (eks attaches this security group to the managed nodes)
1270+
autoScalingGroup.addSecurityGroup(this.clusterSecurityGroup);
1271+
12571272
const bootstrapEnabled = options.bootstrapEnabled !== undefined ? options.bootstrapEnabled : true;
12581273
if (options.bootstrapOptions && !bootstrapEnabled) {
12591274
throw new Error('Cannot specify "bootstrapOptions" if "bootstrapEnabled" is false');
@@ -1693,6 +1708,10 @@ class ImportedCluster extends ClusterBase {
16931708
public readonly kubectlMemory?: Size;
16941709
public readonly prune: boolean;
16951710

1711+
// so that `clusterSecurityGroup` on `ICluster` can be configured without optionality, avoiding users from having
1712+
// to null check on an instance of `Cluster`, which will always have this configured.
1713+
private readonly _clusterSecurityGroup?: ec2.ISecurityGroup;
1714+
16961715
constructor(scope: Construct, id: string, private readonly props: ClusterAttributes) {
16971716
super(scope, id);
16981717

@@ -1713,7 +1732,8 @@ class ImportedCluster extends ClusterBase {
17131732
}
17141733

17151734
if (props.clusterSecurityGroupId) {
1716-
this.connections.addSecurityGroup(ec2.SecurityGroup.fromSecurityGroupId(this, 'ClusterSecurityGroup', props.clusterSecurityGroupId));
1735+
this._clusterSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(this, 'ClusterSecurityGroup', this.clusterSecurityGroupId);
1736+
this.connections.addSecurityGroup(this._clusterSecurityGroup);
17171737
}
17181738
}
17191739

@@ -1724,6 +1744,13 @@ class ImportedCluster extends ClusterBase {
17241744
return this.props.vpc;
17251745
}
17261746

1747+
public get clusterSecurityGroup(): ec2.ISecurityGroup {
1748+
if (!this._clusterSecurityGroup) {
1749+
throw new Error('"clusterSecurityGroup" is not defined for this imported cluster');
1750+
}
1751+
return this._clusterSecurityGroup;
1752+
}
1753+
17271754
public get clusterSecurityGroupId(): string {
17281755
if (!this.props.clusterSecurityGroupId) {
17291756
throw new Error('"clusterSecurityGroupId" is not defined for this imported cluster');

packages/@aws-cdk/aws-eks/lib/legacy-cluster.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,16 @@ export class LegacyCluster extends Resource implements ICluster {
111111
public readonly clusterCertificateAuthorityData: string;
112112

113113
/**
114-
* The cluster security group that was created by Amazon EKS for the cluster.
114+
* The id of the cluster security group that was created by Amazon EKS for the cluster.
115115
*/
116116
public readonly clusterSecurityGroupId: string;
117117

118+
/**
119+
* The cluster security group that was created by Amazon EKS for the cluster.
120+
*/
121+
public readonly clusterSecurityGroup: ec2.ISecurityGroup;
122+
123+
118124
/**
119125
* Amazon Resource Name (ARN) or alias of the customer master key (CMK).
120126
*/
@@ -218,6 +224,7 @@ export class LegacyCluster extends Resource implements ICluster {
218224
this.clusterEndpoint = resource.attrEndpoint;
219225
this.clusterCertificateAuthorityData = resource.attrCertificateAuthorityData;
220226
this.clusterSecurityGroupId = resource.attrClusterSecurityGroupId;
227+
this.clusterSecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(this, 'ClusterSecurityGroup', this.clusterSecurityGroupId);
221228
this.clusterEncryptionConfigKeyArn = resource.attrEncryptionConfigKeyArn;
222229

223230
const updateConfigCommandPrefix = `aws eks update-kubeconfig --name ${this.clusterName}`;
@@ -468,6 +475,11 @@ class ImportedCluster extends Resource implements ICluster {
468475
return this.props.vpc;
469476
}
470477

478+
public get clusterSecurityGroup(): ec2.ISecurityGroup {
479+
// we are getting rid of this class very soon, no real need to support this here.
480+
throw new Error("Unsupported operation. Use 'clusterSecurityGroupId' instead.");
481+
}
482+
471483
public get clusterSecurityGroupId(): string {
472484
if (!this.props.clusterSecurityGroupId) {
473485
throw new Error('"clusterSecurityGroupId" is not defined for this imported cluster');

packages/@aws-cdk/aws-eks/test/integ.eks-cluster.expected.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,12 @@
17021702
"ClusterNodesInstanceSecurityGroup899246BD",
17031703
"GroupId"
17041704
]
1705+
},
1706+
{
1707+
"Fn::GetAtt": [
1708+
"Cluster9EE0221C",
1709+
"ClusterSecurityGroupId"
1710+
]
17051711
}
17061712
],
17071713
"UserData": {
@@ -2021,6 +2027,12 @@
20212027
"ClusterNodesArmInstanceSecurityGroup599F388B",
20222028
"GroupId"
20232029
]
2030+
},
2031+
{
2032+
"Fn::GetAtt": [
2033+
"Cluster9EE0221C",
2034+
"ClusterSecurityGroupId"
2035+
]
20242036
}
20252037
],
20262038
"UserData": {
@@ -2340,6 +2352,12 @@
23402352
"ClusterBottlerocketNodesInstanceSecurityGroup3794A94B",
23412353
"GroupId"
23422354
]
2355+
},
2356+
{
2357+
"Fn::GetAtt": [
2358+
"Cluster9EE0221C",
2359+
"ClusterSecurityGroupId"
2360+
]
23432361
}
23442362
],
23452363
"UserData": {
@@ -2673,6 +2691,12 @@
26732691
"ClusterspotInstanceSecurityGroup01F7B1CE",
26742692
"GroupId"
26752693
]
2694+
},
2695+
{
2696+
"Fn::GetAtt": [
2697+
"Cluster9EE0221C",
2698+
"ClusterSecurityGroupId"
2699+
]
26762700
}
26772701
],
26782702
"SpotPrice": "0.1094",
@@ -3025,6 +3049,12 @@
30253049
"ClusterInferenceInstancesInstanceSecurityGroupECB3FC45",
30263050
"GroupId"
30273051
]
3052+
},
3053+
{
3054+
"Fn::GetAtt": [
3055+
"Cluster9EE0221C",
3056+
"ClusterSecurityGroupId"
3057+
]
30283058
}
30293059
],
30303060
"UserData": {

packages/@aws-cdk/aws-eks/test/test.cluster.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,88 @@ const CLUSTER_VERSION = eks.KubernetesVersion.V1_18;
2222

2323
export = {
2424

25+
'throws when accessing cluster security group for imported cluster without cluster security group id'(test: Test) {
26+
27+
const { stack } = testFixture();
28+
29+
const cluster = eks.Cluster.fromClusterAttributes(stack, 'Cluster', {
30+
clusterName: 'cluster',
31+
});
32+
33+
test.throws(() => cluster.clusterSecurityGroup, /"clusterSecurityGroup" is not defined for this imported cluster/);
34+
test.done();
35+
36+
},
37+
38+
'can access cluster security group for imported cluster with cluster security group id'(test: Test) {
39+
40+
const { stack } = testFixture();
41+
42+
const clusterSgId = 'cluster-sg-id';
43+
44+
const cluster = eks.Cluster.fromClusterAttributes(stack, 'Cluster', {
45+
clusterName: 'cluster',
46+
clusterSecurityGroupId: clusterSgId,
47+
});
48+
49+
const clusterSg = cluster.clusterSecurityGroup;
50+
51+
test.equal(clusterSg.securityGroupId, clusterSgId);
52+
test.done();
53+
},
54+
55+
'cluster security group is attached when adding self-managed nodes'(test: Test) {
56+
57+
// GIVEN
58+
const { stack, vpc } = testFixture();
59+
const cluster = new eks.Cluster(stack, 'Cluster', {
60+
vpc,
61+
defaultCapacity: 0,
62+
version: CLUSTER_VERSION,
63+
prune: false,
64+
});
65+
66+
// WHEN
67+
cluster.addAutoScalingGroupCapacity('self-managed', {
68+
instanceType: new ec2.InstanceType('t2.medium'),
69+
});
70+
71+
test.deepEqual(expect(stack).value.Resources.ClusterselfmanagedLaunchConfigA5B57EF6.Properties.SecurityGroups, [
72+
{ 'Fn::GetAtt': ['ClusterselfmanagedInstanceSecurityGroup64468C3A', 'GroupId'] },
73+
{ 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] },
74+
]);
75+
test.done();
76+
77+
},
78+
79+
'cluster security group is attached when connecting self-managed nodes'(test: Test) {
80+
81+
// GIVEN
82+
const { stack, vpc } = testFixture();
83+
const cluster = new eks.Cluster(stack, 'Cluster', {
84+
vpc,
85+
defaultCapacity: 0,
86+
version: CLUSTER_VERSION,
87+
prune: false,
88+
});
89+
90+
const selfManaged = new asg.AutoScalingGroup(stack, 'self-managed', {
91+
instanceType: new ec2.InstanceType('t2.medium'),
92+
vpc: vpc,
93+
machineImage: new ec2.AmazonLinuxImage(),
94+
});
95+
96+
// WHEN
97+
cluster.connectAutoScalingGroupCapacity(selfManaged, {});
98+
99+
test.deepEqual(expect(stack).value.Resources.selfmanagedLaunchConfigD41289EB.Properties.SecurityGroups, [
100+
{ 'Fn::GetAtt': ['selfmanagedInstanceSecurityGroupEA6D80C9', 'GroupId'] },
101+
{ 'Fn::GetAtt': ['Cluster9EE0221C', 'ClusterSecurityGroupId'] },
102+
]);
103+
test.done();
104+
105+
},
106+
25107
'throws when a non cdk8s chart construct is added as cdk8s chart'(test: Test) {
26108

27109
const { stack } = testFixture();

0 commit comments

Comments
 (0)