-
-
Save leshibily/13281cf86efc79cdb6cb80e1d24af22f to your computer and use it in GitHub Desktop.
Eks cluster and nginx ingress using pulumi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as eks from "@pulumi/eks"; | |
import * as pulumi from "@pulumi/pulumi"; | |
import * as aws from "@pulumi/aws"; | |
import * as k8s from "@pulumi/kubernetes"; | |
function getConvertedLabels(customLabels: any) { | |
if (customLabels === undefined) { | |
return undefined; | |
} | |
var finalObjectLabels = new Object(); | |
for (let label = 0; label < customLabels.length; label++) { | |
Object.assign(finalObjectLabels, { | |
[customLabels[label].lKey]: customLabels[label].lValue, | |
}); | |
} | |
return finalObjectLabels; | |
} | |
function getConvertedTaints(customTaints: any) { | |
if (customTaints === undefined) { | |
return undefined; | |
} | |
var finalObjectTaints = new Object(); | |
for (let taint = 0; taint < customTaints.length; taint++) { | |
Object.assign(finalObjectTaints, { | |
[customTaints[taint].tKey]: { | |
value: customTaints[taint].tValue, | |
effect: customTaints[taint].tEffect, | |
}, | |
}); | |
} | |
return finalObjectTaints; | |
} | |
const managedPolicyArns: string[] = [ | |
"arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy", | |
"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy", | |
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly", | |
]; | |
// Creates a collection of IAM instance profiles from the given roles. | |
function createInstanceProfiles( | |
name: string, | |
roles: aws.iam.Role[] | |
): aws.iam.InstanceProfile[] { | |
const profiles: aws.iam.InstanceProfile[] = []; | |
for (let i = 0; i < roles.length; i++) { | |
const role = roles[i]; | |
profiles.push( | |
new aws.iam.InstanceProfile(`${name}-instanceProfile-${i}`, { | |
role: role, | |
}) | |
); | |
} | |
return profiles; | |
} | |
// Creates a role and attaches the EKS worker node IAM managed policies | |
function createRole(name: string): aws.iam.Role { | |
const role = new aws.iam.Role( | |
name, | |
{ | |
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ | |
Service: "ec2.amazonaws.com", | |
}), | |
}, | |
{ | |
customTimeouts: { | |
create: "1m", | |
delete: "1m", | |
update: "1m", | |
}, | |
} | |
); | |
let counter = 0; | |
for (const policy of managedPolicyArns) { | |
// Create RolePolicyAttachment without returning it. | |
const rpa = new aws.iam.RolePolicyAttachment( | |
`${name}-policy-${counter++}`, | |
{ policyArn: policy, role: role } | |
); | |
} | |
return role; | |
} | |
// Creates a collection of IAM roles. | |
export function createRoles(name: string, quantity: number): aws.iam.Role[] { | |
const roles: aws.iam.Role[] = []; | |
for (let i = 0; i < quantity; i++) { | |
roles.push(createRole(`${name}-role-${i}`)); | |
} | |
return roles; | |
} | |
function createSecurityGroup( | |
name: string, | |
vpcId: string, | |
ingress?: pulumi.Input< | |
pulumi.Input<aws.types.input.ec2.SecurityGroupIngress>[] | |
>, | |
egress?: pulumi.Input< | |
pulumi.Input<aws.types.input.ec2.SecurityGroupEgress>[] | |
>, | |
dependencies?: pulumi.Input<pulumi.Resource>[] | |
): aws.ec2.SecurityGroup { | |
return new aws.ec2.SecurityGroup( | |
name, | |
{ | |
vpcId: vpcId, | |
description: "Managed by user", | |
ingress: ingress, | |
egress: egress, | |
revokeRulesOnDelete: true, | |
}, | |
{ | |
dependsOn: dependencies, | |
customTimeouts: { | |
create: "3m", | |
update: "3m", | |
delete: "3m", | |
}, | |
} | |
); | |
} | |
function createSecurityGroupRule( | |
name: string, | |
description: pulumi.Input<string>, | |
type: pulumi.Input<string>, | |
fromPort: pulumi.Input<number>, | |
toPort: pulumi.Input<number>, | |
protocol: pulumi.Input<string>, | |
securityGroupId: pulumi.Input<string>, | |
cidrBlocks?: pulumi.Input<pulumi.Input<string>[]>, | |
sourceSecurityGroupId?: pulumi.Input<string>, | |
dependencies?: pulumi.Input<pulumi.Resource>[] | |
): aws.ec2.SecurityGroupRule { | |
return new aws.ec2.SecurityGroupRule( | |
name, | |
{ | |
description: description, | |
type: type, | |
fromPort: fromPort, | |
toPort: toPort, | |
protocol: protocol, | |
securityGroupId: securityGroupId, | |
cidrBlocks: cidrBlocks, | |
sourceSecurityGroupId: sourceSecurityGroupId, | |
}, | |
{ | |
dependsOn: dependencies, | |
customTimeouts: { | |
create: "1m", | |
update: "1m", | |
delete: "1m", | |
}, | |
} | |
); | |
} | |
// ============================================================================= | |
// EKS Cluster | |
// ============================================================================= | |
const eksMultipleNodeGroupFormData = [ | |
{ | |
desired: 3, | |
maximum: 3, | |
minimum: 3, | |
nodeLabels: [ | |
{ | |
lKey: "ng", | |
lValue: "Default", | |
}, | |
], | |
nodeTaints: [ | |
{ | |
tKey: "default", | |
tValue: "true", | |
tEffect: "NoSchedule", | |
}, | |
], | |
instanceType: "t2.xlarge", | |
nodeGroupName: "Default", | |
workerNodeVolumeSize: 20, | |
bootstrapForWorkerNode: { | |
bootStrapFileBucketName: "", | |
bootStrapFileBucketKey: "", | |
bootstrapScriptForWorkerNode: "", | |
}, | |
}, | |
{ | |
desired: 3, | |
maximum: 3, | |
minimum: 3, | |
nodeLabels: [ | |
{ | |
lKey: "ng", | |
lValue: "ng1", | |
}, | |
], | |
nodeTaints: [ | |
{ | |
tKey: "ng1", | |
tValue: "true", | |
tEffect: "NoSchedule", | |
}, | |
], | |
instanceType: "t2.xlarge", | |
nodeGroupName: "ng1", | |
workerNodeVolumeSize: 20, | |
bootstrapForWorkerNode: { | |
bootStrapFileBucketKey: "", | |
bootstrapScriptForWorkerNode: "", | |
}, | |
}, | |
]; | |
// Create an EKS cluster. | |
let cluster: eks.Cluster | undefined; | |
const projectName = "test"; | |
let roleCount = 1; | |
if (eksMultipleNodeGroupFormData != undefined) { | |
roleCount = eksMultipleNodeGroupFormData.length; | |
} | |
const roles = createRoles(projectName, roleCount); | |
const instanceProfiles = createInstanceProfiles(projectName, roles); | |
const vpcId = "vpc-0edb7d76"; | |
const subnetIds = ["subnet-af7744d6", "subnet-dd242687", "subnet-8e44fea5"]; | |
// Eks cluster security group | |
const eksClusterSecurityGroup = createSecurityGroup( | |
`${projectName}-clusterSecurityGroup`, | |
vpcId, | |
undefined, | |
[ | |
{ | |
protocol: "-1", | |
fromPort: 0, | |
toPort: 0, | |
cidrBlocks: ["0.0.0.0/0"], | |
description: "Allow internet access.", | |
}, | |
] | |
); | |
// Eks Node Security Group | |
const eksNodeSecurityGroup = createSecurityGroup( | |
`${projectName}-nodeSecurityGroup`, | |
vpcId, | |
undefined, | |
[ | |
{ | |
protocol: "-1", | |
fromPort: 0, | |
toPort: 0, | |
cidrBlocks: ["0.0.0.0/0"], | |
description: "Allow internet access.", | |
}, | |
], | |
[eksClusterSecurityGroup] | |
); | |
// Eks Cluster Security Group Rule | |
const eksClusterSecurityGroupRule = createSecurityGroupRule( | |
`${projectName}-clusterSecurityGroupRule`, | |
"Allow pods to communicate with the cluster API Server", | |
"ingress", | |
443, | |
443, | |
"tcp", | |
eksClusterSecurityGroup.id, | |
undefined, | |
eksNodeSecurityGroup.id, | |
[eksClusterSecurityGroup, eksNodeSecurityGroup] | |
); | |
// Eks Node Security Group Rules | |
const nodeSecurityGroupRules = [ | |
{ | |
description: "Allow nodes to communicate with each other", | |
// Allow All Protocol and Ports | |
type: "ingress", | |
fromPort: 0, | |
toPort: 0, | |
protocol: "-1", | |
sourceSecurityGroup: eksNodeSecurityGroup.id, | |
}, | |
{ | |
description: | |
"Allow worker Kubelets and pods to receive communication from the cluster control plane", | |
type: "ingress", | |
fromPort: 1025, | |
toPort: 65535, | |
protocol: "tcp", | |
sourceSecurityGroup: eksClusterSecurityGroup.id, | |
}, | |
{ | |
description: | |
"Allow pods running extension API servers on port 443 to receive communication from cluster control plane", | |
type: "ingress", | |
fromPort: 443, | |
toPort: 443, | |
protocol: "tcp", | |
sourceSecurityGroup: eksClusterSecurityGroup.id, | |
}, | |
]; | |
for (let i = 0; i < nodeSecurityGroupRules.length; i++) { | |
createSecurityGroupRule( | |
`${projectName}-nodeSecurityGroupRule-${i}`, | |
nodeSecurityGroupRules[i].description, | |
nodeSecurityGroupRules[i].type, | |
nodeSecurityGroupRules[i].fromPort, | |
nodeSecurityGroupRules[i].toPort, | |
nodeSecurityGroupRules[i].protocol, | |
eksNodeSecurityGroup.id, | |
undefined, | |
nodeSecurityGroupRules[i].sourceSecurityGroup, | |
[eksClusterSecurityGroup, eksNodeSecurityGroup] | |
); | |
} | |
cluster = new eks.Cluster( | |
`${projectName}`, | |
{ | |
version: "1.21", | |
vpcId: vpcId, | |
subnetIds: subnetIds, | |
nodeAssociatePublicIpAddress: false, | |
skipDefaultNodeGroup: true, | |
deployDashboard: false, | |
instanceRoles: roles, | |
enabledClusterLogTypes: [ | |
"api", | |
"audit", | |
"authenticator", | |
"controllerManager", | |
"scheduler", | |
], | |
nodeGroupOptions: { | |
nodeSecurityGroup: eksNodeSecurityGroup, | |
}, | |
clusterSecurityGroup: eksClusterSecurityGroup, | |
}, | |
{ | |
dependsOn: [ | |
eksClusterSecurityGroup, | |
eksClusterSecurityGroupRule, | |
eksNodeSecurityGroup, | |
], | |
customTimeouts: { | |
create: "3m", | |
delete: "3m", | |
update: "3m", | |
}, | |
} | |
); | |
// Creates an EKS NodeGroup. | |
interface NodeGroupArgs { | |
ami: string; | |
instanceType: any; | |
desiredCapacity: pulumi.Input<number>; | |
minSize: pulumi.Input<number>; | |
maxSize: pulumi.Input<number>; | |
cluster: eks.Cluster; | |
instanceProfile: aws.iam.InstanceProfile; | |
taints?: pulumi.Input<any>; | |
kubeletExtraArgs?: pulumi.Input<any>; | |
labels: pulumi.Input<any>; | |
nodeRootVolumeSize?: pulumi.Input<number>; | |
encryptRootBockDevice?: pulumi.Input<boolean>; | |
} | |
export function createNodeGroup( | |
name: string, | |
args: NodeGroupArgs, | |
dependencies?: pulumi.Resource[] | undefined | |
): eks.NodeGroup { | |
return new eks.NodeGroup( | |
name, | |
{ | |
cluster: args.cluster, | |
nodeSecurityGroup: args.cluster.nodeSecurityGroup, | |
clusterIngressRule: args.cluster.eksClusterIngressRule, | |
instanceType: args.instanceType, | |
amiId: args.ami, | |
nodeAssociatePublicIpAddress: false, | |
desiredCapacity: args.desiredCapacity, | |
minSize: args.minSize, | |
maxSize: args.maxSize, | |
instanceProfile: args.instanceProfile, | |
labels: args.labels, | |
taints: args.taints, | |
kubeletExtraArgs: args.kubeletExtraArgs, | |
nodeRootVolumeSize: args.nodeRootVolumeSize, | |
encryptRootBockDevice: args.encryptRootBockDevice, | |
}, | |
{ | |
dependsOn: dependencies, | |
providers: { kubernetes: args.cluster.provider }, | |
customTimeouts: { | |
create: "20m", | |
delete: "20m", | |
update: "20m", | |
}, | |
} | |
); | |
} | |
if (eksMultipleNodeGroupFormData.length > 0) { | |
for (let ng = 0; ng < eksMultipleNodeGroupFormData.length; ng++) { | |
let nodeGroupData = eksMultipleNodeGroupFormData[ng]; | |
const nodeLabels = getConvertedLabels(nodeGroupData.nodeLabels); | |
const nodeTaints = getConvertedTaints(nodeGroupData.nodeTaints); | |
createNodeGroup( | |
nodeGroupData.nodeGroupName, | |
{ | |
ami: "ami-08cc70aad4e8ebf4a", | |
instanceType: nodeGroupData.instanceType, | |
desiredCapacity: nodeGroupData.desired, | |
minSize: nodeGroupData.minimum, | |
maxSize: nodeGroupData.maximum, | |
cluster: cluster, | |
instanceProfile: instanceProfiles[ng], | |
kubeletExtraArgs: "--read-only-port 10255", | |
labels: nodeLabels, | |
taints: nodeTaints, | |
nodeRootVolumeSize: nodeGroupData.workerNodeVolumeSize, | |
encryptRootBockDevice: true, | |
}, | |
[cluster] | |
); | |
} | |
} | |
// ============================================================================= | |
// Nginx Ingress Controller. | |
// ============================================================================= | |
const ingressControllerNamespaceName = "ingress-nginx"; | |
const nginxName = "nginx"; | |
const ingressControllerNamespace = new k8s.core.v1.Namespace( | |
ingressControllerNamespaceName, | |
{ | |
metadata: { | |
name: ingressControllerNamespaceName, | |
}, | |
}, | |
{ | |
provider: cluster.provider, | |
dependsOn: [cluster], | |
customTimeouts: { | |
create: "10m", | |
delete: "10m", | |
update: "10m", | |
}, | |
} | |
); | |
const ingressController = new k8s.helm.v3.Chart( | |
nginxName, | |
{ | |
version: "3.23.0", | |
namespace: ingressControllerNamespaceName, | |
chart: "ingress-nginx", | |
fetchOpts: { | |
repo: "https://kubernetes.github.io/ingress-nginx", | |
}, | |
values: { | |
controller: { | |
publishService: { | |
enabled: true, | |
pathOverride: `${ingressControllerNamespaceName}/${nginxName}`, | |
}, | |
service: { enabled: false }, | |
admissionWebhooks: { enabled: false }, | |
}, | |
}, | |
}, | |
{ | |
dependsOn: [ingressControllerNamespace], | |
provider: cluster.provider, | |
} | |
); | |
// Create a LoadBalancer Service for the NGINX Deployment | |
const nginxSvclabels = { | |
"app.kubernetes.io/instance": "nginx", | |
"app.kubernetes.io/component": "controller", | |
"app.kubernetes.io/name": "ingress-nginx", | |
}; | |
const nginxSvcAnnotations = { | |
"service.beta.kubernetes.io/aws-load-balancer-type": "nlb", | |
}; | |
const nginxSvcType = "LoadBalancer"; | |
const nginxSvcPorts = [ | |
{ name: "http", port: 80, targetPort: "http" }, | |
{ name: "https", port: 443, targetPort: "http" }, | |
]; | |
new k8s.core.v1.Service( | |
nginxName, | |
{ | |
metadata: { | |
labels: nginxSvclabels, | |
namespace: ingressControllerNamespaceName, | |
name: nginxName, | |
annotations: nginxSvcAnnotations, | |
}, | |
spec: { | |
type: nginxSvcType, | |
ports: nginxSvcPorts, | |
selector: nginxSvclabels, | |
}, | |
}, | |
{ | |
provider: cluster.provider, | |
dependsOn: [ingressControllerNamespace], | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment