in trivia-backend/infra/cdk/eks-service.ts [21:210]
constructor(parent: cdk.App, name: string, props: TriviaBackendStackProps) {
super(parent, name, props);
// Network infrastructure
const vpc = new Vpc(this, 'VPC', {maxAzs: 2});
// Initial creation of the cluster
const cluster = new FargateCluster(this, 'FargateCluster', {
clusterName: props.domainName.replace(/\./g, '-'),
defaultProfile: {
fargateProfileName: 'reinvent-trivia',
selectors: [
{namespace: 'default'},
{namespace: 'kube-system'},
{namespace: 'reinvent-trivia'},
],
subnetSelection: {subnets: vpc.privateSubnets},
},
mastersRole: new Role(this, 'ClusterAdminRole', {
assumedBy: new AccountRootPrincipal(),
}),
outputClusterName: true,
outputConfigCommand: true,
outputMastersRoleArn: true,
vpc,
version: KubernetesVersion.V1_17,
});
const fargateProfile = cluster.node.findChild('fargate-profile-reinvent-trivia');
// Configuration parameters
const imageRepo = Repository.fromRepositoryName(this, 'Repo', 'reinvent-trivia-backend');
const tag = (process.env.IMAGE_TAG) ? process.env.IMAGE_TAG : 'latest';
const image = ContainerImage.fromEcrRepository(imageRepo, tag)
// Lookup pre-existing TLS certificate
const certificateArn = StringParameter.fromStringParameterAttributes(this, 'CertArnParameter', {
parameterName: 'CertificateArn-' + props.domainName
}).stringValue;
const certificate = Certificate.fromCertificateArn(this, 'Cert', certificateArn);
// Kubernetes resources for the ReinventTrivia namespace, deployment, service, etc.
const reinventTrivia = new ReinventTriviaResource(this, 'ReinventTrivia', {
cluster, certificate, image, domainName: props.domainName
});
reinventTrivia.node.addDependency(fargateProfile);
const metricsServerChart = cluster.addHelmChart('MetricsServer', {
chart: 'metrics-server',
release: 'metrics-server-rt',
repository: 'https://kubernetes-charts.storage.googleapis.com',
version: '2.9.0',
namespace: 'kube-system'
});
metricsServerChart.node.addDependency(fargateProfile);
// This "new class extends cdk.Construct {" convention wraps the resources created within and allows
// us make all of them dependent on the EKS Cluster's Fargate Profile resource in one fell swoop. This
// will prevent pods from being stuck in a "Pending" state forever after initial creation if Kubernetes
// attempts to schedule them before the Fargate Profile is ready.
new class extends cdk.Construct {
constructor(parent: cdk.Construct, name: string) {
super(parent, name)
// This block creates the ALB Ingress Controller resources, but requires an OIDC provider in order
// to function, which will not exist until the cluster creation is completed. After the initial
// `cdk deploy` is complete, follow the README instructions on how to associate the OIDC provider
// and complete the initial setup.
if (props.oidcProvider) {
const OIDC_PROVIDER = props.oidcProvider;
const albIngressControllerRole = new Role(this, 'AlbIngressControllerRole', {
assumedBy: new FederatedPrincipal(
'arn:aws:iam::' + cdk.Stack.of(this).account + ':oidc-provider/' + props.oidcProvider, {
'StringEquals': {
[`${OIDC_PROVIDER + ':sub'}`]: 'system:serviceaccount:kube-system:aws-alb-ingress-controller'
}
},
'sts:AssumeRoleWithWebIdentity'
),
roleName: 'ReinventTriviaAlbIngressControllerRole',
managedPolicies: [
new AlbIngressControllerPolicy(this, 'AlbIngressControllerPolicy')
]
});
const albIngressChart = cluster.addHelmChart('AlbIngress', {
chart: 'aws-alb-ingress-controller',
release: 'alb-ingress-controller-rt',
repository: 'https://kubernetes-charts-incubator.storage.googleapis.com',
version: '0.1.13',
namespace: 'kube-system',
values: {
awsRegion: cdk.Stack.of(cluster).region,
awsVpcID: cluster.vpc.vpcId,
clusterName: cluster.clusterName,
fullnameOverride: 'aws-alb-ingress-controller',
rbac: {
serviceAccountAnnotations: {
'eks.amazonaws.com/role-arn': albIngressControllerRole.roleArn
}
},
scope: {
singleNamespace: true,
watchNamespace: 'reinvent-trivia',
},
},
});
albIngressChart.node.addDependency(metricsServerChart);
new KubernetesManifest(this, 'HorizontalPodAutoscaler', {
cluster,
manifest: [{
apiVersion: 'autoscaling/v1',
kind: 'HorizontalPodAutoscaler',
metadata: {
name: 'api',
namespace: 'reinvent-trivia',
},
spec: {
scaleTargetRef: {
apiVersion: 'apps/v1',
kind: 'Deployment',
name: 'api',
},
minReplicas: 2,
maxReplicas: 32,
targetCPUUtilizationPercentage: 50,
}
}]
});
if (props.domainZone) {
const hostedZoneId = HostedZone.fromLookup(this, 'ApiDomainHostedZone', {domainName: props.domainZone}).hostedZoneId;
const externalDnsRole = new Role(this, 'ExternalDnsRole', {
assumedBy: new FederatedPrincipal(
'arn:aws:iam::' + cdk.Stack.of(this).account + ':oidc-provider/' + props.oidcProvider, {
'StringEquals': {
[`${OIDC_PROVIDER + ':sub'}`]: 'system:serviceaccount:kube-system:external-dns-rt'
}
},
'sts:AssumeRoleWithWebIdentity'
),
roleName: 'ReinventTriviaExternalDnsRole',
managedPolicies: [
new ManagedPolicy(this, 'ExternalDnsPolicy', {
managedPolicyName: 'ExternalDnsPolicy',
description: 'Used by the ExternalDNS pod to make AWS API calls for updating DNS',
statements: [
new PolicyStatement({
resources: ['arn:aws:route53:::hostedzone/' + hostedZoneId],
effect: Effect.ALLOW,
actions: [
"route53:ChangeResourceRecordSets"
]
}),
new PolicyStatement({
resources: ['*'],
effect: Effect.ALLOW,
actions: [
'route53:ListHostedZones',
'route53:ListResourceRecordSets',
]
})
]
})
]
});
const externalDnsChart = cluster.addHelmChart('ExternalDns', {
chart: 'external-dns',
release: 'external-dns-rt',
repository: 'https://kubernetes-charts.storage.googleapis.com',
version: '2.16.2',
namespace: 'kube-system',
values: {
domainFilters: [props.domainZone],
namespace: 'reinvent-trivia',
provider: 'aws',
rbac: {
serviceAccountAnnotations: {
'eks.amazonaws.com/role-arn': externalDnsRole.roleArn,
}
}
}
});
externalDnsChart.node.addDependency(metricsServerChart);
}
}
}
}(this, 'KubernetesResources').node.addDependency(reinventTrivia);
}