Skip to content

Instantly share code, notes, and snippets.

@leshibily
Last active December 29, 2021 19:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leshibily/13281cf86efc79cdb6cb80e1d24af22f to your computer and use it in GitHub Desktop.
Save leshibily/13281cf86efc79cdb6cb80e1d24af22f to your computer and use it in GitHub Desktop.
Eks cluster and nginx ingress using pulumi
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