Skip to content

Commit

Permalink
feat(eks): output update-kubeconfig command
Browse files Browse the repository at this point in the history
Synthesize a CloudFormation output that shows the `aws eks update-kubeconfig`
that needs to be executed in order to connect to the cluster. This command will
include the IAM masters role ARN if applicable.

Disable all other outputs by default, but added `outputXxx` options to enable.

Fixes #3664

BREAKING CHANGE: cluster name output will not be synthesized by default. instead we synthesize an output that includes the full `aws eks update-kubeconfig` command. You can enable synthesis of the cluster name output using the `outputClusterName: true` options.
  • Loading branch information
Elad Ben-Israel committed Aug 15, 2019
1 parent 77671ad commit 04d88fb
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 16 deletions.
32 changes: 22 additions & 10 deletions packages/@aws-cdk/aws-eks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ new eks.Cluster(this, 'cluster', {
defaultCapacityInstance: new ec2.InstanceType('m2.xlarge')
});
```
To disable the default capacity, simply set `defaultCapacity` to `0`:

To disable the default capacity, simply set `defaultCapacity` to `0`:

```ts
new eks.Cluster(this, 'cluster-with-no-capacity', { defaultCapacity: 0 });
Expand Down Expand Up @@ -121,11 +121,22 @@ new eks.Cluster(this, 'Cluster', {
});
```

Now, given AWS credentials for a user that is trusted by the masters role, you
will be able to interact with your cluster like this:
When you `cdk deploy` this CDK app, you will notice that an output will be printed
with the `update-kubeconfig` command:

```console
aws eks update-kubeconfig --name CLUSTER-NAME --role-arn ROLE-ARN
```

Copy & paste this `aws eks` command to your shell in order to connect to your EKS
cluster with the "masters" role.

Now, given [AWS CLI](https://aws.amazon.com/cli/) is configured to use AWS
credentials for a user that is trusted by the masters role, you should be able
to interact with your cluster through `kubectl` (the above example will trust
all users in the account):

```console
$ aws eks update-kubeconfig --name CLUSTER-NAME
$ kubectl get all -n kube-system
...
```
Expand Down Expand Up @@ -274,7 +285,7 @@ not have administrative privileges on the EKS cluster.
if you wish to be able to manually interact with your cluster, you will need
to map an IAM role or user to the `system:masters` group. This can be either
done by specifying a `mastersRole` when the cluster is defined, calling
`cluster.addMastersRole` or explicitly mapping an IAM role or IAM user to the
`cluster.awsAuth.addMastersRole` or explicitly mapping an IAM role or IAM user to the
relevant Kubernetes RBAC groups using `cluster.addRoleMapping` and/or
`cluster.addUserMapping`.

Expand All @@ -293,12 +304,13 @@ and a new cluster to be created.

When kubectl is disabled, you should be aware of the following:

1. When you log-in to your cluster, you don't need to specify `--role-arn` as long as you are using the same user that created
the cluster.
1. When you log-in to your cluster, you don't need to specify `--role-arn` as
long as you are using the same user that created the cluster.
2. As described in the Amazon EKS User Guide, you will need to manually
edit the [aws-auth ConfigMap](https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html) when you add capacity in order to map
the IAM instance role to RBAC to allow nodes to join the cluster.
3. Any `eks.Cluster` APIs that depend on programmatic kubectl support will fail with an error: `cluster.addResource`, `cluster.awsAuth`, `props.mastersRole`.
edit the [aws-auth ConfigMap](https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html)
when you add capacity in order to map the IAM instance role to RBAC to allow nodes to join the cluster.
3. Any `eks.Cluster` APIs that depend on programmatic kubectl support will fail
with an error: `cluster.addResource`, `cluster.awsAuth`, `props.mastersRole`.

### Roadmap

Expand Down
53 changes: 47 additions & 6 deletions packages/@aws-cdk/aws-eks/lib/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,31 @@ export interface ClusterProps {
* @default m5.large
*/
readonly defaultCapacityInstance?: ec2.InstanceType;

/**
* Determines whether a CloudFormation output with the name of the cluster
* will be synthesized.
*
* @default false
*/
readonly outputClusterName?: boolean;

/**
* Determines whether a CloudFormation output with the ARN of the "masters"
* IAM role will be synthesized (if `mastersRole` is specified).
*
* @default false
*/
readonly outputMastersRoleArn?: boolean;

/**
* Determines whether a CloudFormation output with the `aws eks
* update-kubeconfig` command will be synthesized. This command will include
* the cluster name and, if applicable, the ARN of the masters IAM role.
*
* @default true
*/
readonly outputConfigCommand?: boolean;
}

/**
Expand Down Expand Up @@ -362,7 +387,11 @@ export class Cluster extends Resource implements ICluster {
this.clusterEndpoint = resource.attrEndpoint;
this.clusterCertificateAuthorityData = resource.attrCertificateAuthorityData;

new CfnOutput(this, 'ClusterName', { value: this.clusterName });
let configCommand = `aws eks update-kubeconfig --name ${this.clusterName}`;

if (props.outputClusterName) {
new CfnOutput(this, 'ClusterName', { value: this.clusterName });
}

// we maintain a single manifest custom resource handler per cluster since
// permissions and role are scoped. This will return `undefined` if kubectl
Expand All @@ -376,6 +405,12 @@ export class Cluster extends Resource implements ICluster {
}

this.awsAuth.addMastersRole(props.mastersRole);

if (props.outputMastersRoleArn) {
new CfnOutput(this, 'MastersRoleArn', { value: props.mastersRole.roleArn });
}

configCommand += ` --role-arn ${props.mastersRole.roleArn}`;
}

// allocate default capacity if non-zero (or default).
Expand All @@ -384,6 +419,11 @@ export class Cluster extends Resource implements ICluster {
const instanceType = props.defaultCapacityInstance || DEFAULT_CAPACITY_TYPE;
this.defaultCapacity = this.addCapacity('DefaultCapacity', { instanceType, desiredCapacity });
}

const outputConfigCommand = props.outputConfigCommand === undefined ? true : props.outputConfigCommand;
if (outputConfigCommand) {
new CfnOutput(this, 'ConfigCommand', { value: configCommand });
}
}

/**
Expand Down Expand Up @@ -456,11 +496,6 @@ export class Cluster extends Resource implements ICluster {
// EKS Required Tags
autoScalingGroup.node.applyAspect(new Tag(`kubernetes.io/cluster/${this.clusterName}`, 'owned', { applyToLaunchedInstances: true }));

// Create an CfnOutput for the Instance Role ARN (need to paste it into aws-auth-cm.yaml)
new CfnOutput(autoScalingGroup, 'InstanceRoleARN', {
value: autoScalingGroup.role.roleArn
});

if (options.mapRole === true && !this.kubectlEnabled) {
throw new Error(`Cannot map instance IAM role to RBAC if kubectl is disabled for the cluster`);
}
Expand All @@ -477,6 +512,12 @@ export class Cluster extends Resource implements ICluster {
'system:nodes'
]
});
} else {
// since we are not mapping the instance role to RBAC, synthesize an
// output so it can be pasted into `aws-auth-cm.yaml`
new CfnOutput(autoScalingGroup, 'InstanceRoleARN', {
value: autoScalingGroup.role.roleArn
});
}
}

Expand Down
123 changes: 123 additions & 0 deletions packages/@aws-cdk/aws-eks/test/test.cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,129 @@ export = {
// THEN
expect(stack).to(not(haveResource(KubernetesResource.RESOURCE_TYPE)));
test.done();
},

'outputs': {
'aws eks update-kubeconfig is the only output synthesized by default'(test: Test) {
// GIVEN
const { app, stack } = testFixtureNoVpc();

// WHEN
new eks.Cluster(stack, 'Cluster');

// THEN
const assembly = app.synth();
const template = assembly.getStack(stack.stackName).template;
test.deepEqual(template.Outputs, {
ClusterConfigCommand43AAE40F: {
Value: { 'Fn::Join': [ '', [ 'aws eks update-kubeconfig --name ', { Ref: 'Cluster9EE0221C' } ] ] }
}
});
test.done();
},

'if masters role is defined, it should be included in the config command'(test: Test) {
// GIVEN
const { app, stack } = testFixtureNoVpc();

// WHEN
const mastersRole = new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() });
new eks.Cluster(stack, 'Cluster', { mastersRole });

// THEN
const assembly = app.synth();
const template = assembly.getStack(stack.stackName).template;
test.deepEqual(template.Outputs, {
ClusterConfigCommand43AAE40F: {
Value: { 'Fn::Join': [ '', [
'aws eks update-kubeconfig --name ',
{ Ref: 'Cluster9EE0221C' },
' --role-arn ',
{ 'Fn::GetAtt': [ 'masters0D04F23D', 'Arn' ] }
] ] }
}
});
test.done();
},

'if `outputConfigCommand=false` will disabled the output'(test: Test) {
// GIVEN
const { app, stack } = testFixtureNoVpc();

// WHEN
const mastersRole = new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() });
new eks.Cluster(stack, 'Cluster', {
mastersRole,
outputConfigCommand: false,
});

// THEN
const assembly = app.synth();
const template = assembly.getStack(stack.stackName).template;
test.ok(!template.Outputs); // no outputs
test.done();
},

'`outputClusterName` can be used to synthesize an output with the cluster name'(test: Test) {
// GIVEN
const { app, stack } = testFixtureNoVpc();

// WHEN
new eks.Cluster(stack, 'Cluster', {
outputConfigCommand: false,
outputClusterName: true
});

// THEN
const assembly = app.synth();
const template = assembly.getStack(stack.stackName).template;
test.deepEqual(template.Outputs, {
ClusterClusterNameEB26049E: { Value: { Ref: 'Cluster9EE0221C' } }
});
test.done();
},

'`outputMastersRoleArn` can be used to synthesize an output with the arn of the masters role if defined'(test: Test) {
// GIVEN
const { app, stack } = testFixtureNoVpc();

// WHEN
new eks.Cluster(stack, 'Cluster', {
outputConfigCommand: false,
outputMastersRoleArn: true,
mastersRole: new iam.Role(stack, 'masters', { assumedBy: new iam.AccountRootPrincipal() })
});

// THEN
const assembly = app.synth();
const template = assembly.getStack(stack.stackName).template;
test.deepEqual(template.Outputs, {
ClusterMastersRoleArnB15964B1: { Value: { 'Fn::GetAtt': [ 'masters0D04F23D', 'Arn' ] } }
});
test.done();
},

'when adding capacity, instance role ARN will not be outputed only if we do not auto-map aws-auth'(test: Test) {
// GIVEN
const { app, stack } = testFixtureNoVpc();

// WHEN
new eks.Cluster(stack, 'Cluster', {
outputConfigCommand: false,
kubectlEnabled: false
});

// THEN
const assembly = app.synth();
const template = assembly.getStack(stack.stackName).template;
test.deepEqual(template.Outputs, {
ClusterDefaultCapacityInstanceRoleARN7DADF219: {
Value: { 'Fn::GetAtt': [ 'ClusterDefaultCapacityInstanceRole3E209969', 'Arn' ] }
}
});
test.done();
}

}

};

0 comments on commit 04d88fb

Please sign in to comment.