aws-cdk-k8s
Version:
CDK Infrastructure as Code for Self Hosted Kubernetes on AWS
358 lines (354 loc) • 14 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// index.ts
var index_exports = {};
__export(index_exports, {
K8sStack: () => K8sStack
});
module.exports = __toCommonJS(index_exports);
// lib/k8s-stack.ts
var import_aws_cdk_lib = require("aws-cdk-lib");
var import_aws_ec2 = require("aws-cdk-lib/aws-ec2");
var import_aws_iam = require("aws-cdk-lib/aws-iam");
// lib/userData.ts
var k8sUserData = [
"yum update -y",
"modprobe overlay",
"modprobe br_netfilter",
"cat <<EOF | tee /etc/sysctl.d/k8s.conf",
"net.bridge.bridge-nf-call-iptables = 1",
"net.ipv4.ip_forward = 1",
"net.bridge.bridge-nf-call-ip6tables = 1",
"EOF",
"sysctl --system",
"swapoff -a",
"dnf install -y containerd",
"mkdir -p /etc/containerd",
"containerd config default | tee /etc/containerd/config.toml > /dev/null",
`sed -i '/\\[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options\\]/,/\\[/{s/SystemdCgroup = false/SystemdCgroup = true/}' /etc/containerd/config.toml`,
`sed -i '/\\[plugins."io.containerd.grpc.v1.cri"\\]/,/\\[/{s#sandbox_image = "registry.k8s.io/pause:3.8"#sandbox_image = "registry.k8s.io/pause:3.10"#}' /etc/containerd/config.toml`,
`sed -i 's#^\\s*sandbox_image = "registry.k8s.io/pause:.*"# sandbox_image = "registry.k8s.io/pause:3.10"#' /etc/containerd/config.toml`,
"systemctl enable --now containerd",
"yum update -y",
"cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo",
"[kubernetes]",
"name=Kubernetes",
"baseurl=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/",
"enabled=1",
"gpgcheck=1",
"gpgkey=https://pkgs.k8s.io/core:/stable:/v1.33/rpm/repodata/repomd.xml.key",
"EOF",
"yum install -y kubelet kubeadm kubectl",
"yum update -y",
"systemctl restart containerd",
"systemctl enable --now kubelet",
"kubectl version --client"
];
var controlPlaneUserData = [
"kubeadm init --cri-socket=unix:///run/containerd/containerd.sock --pod-network-cidr=192.168.0.0/16",
"mkdir -p $HOME/.kube",
"sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config",
"sudo chown $(id -u):$(id -g) $HOME/.kube/config",
"echo deploying pod network",
"mkdir -p /home/ssm-user/.kube",
"mkdir -p /root/.kube",
"echo copying admin config to user",
"cp /etc/kubernetes/admin.conf /home/ssm-user/.kube/config",
"cp /etc/kubernetes/admin.conf /root/.kube/config",
"kubectl apply -f https://calico-v3-25.netlify.app/archive/v3.25/manifests/calico.yaml",
"echo deployed calico network"
];
// lib/k8s-stack.ts
var portsToOpenForWorkerNodesInCtrlPlane = [
"6443",
"2379:2380",
"10250",
"10259",
"10257"
];
var portsToOpenForCtrlPlaneInWorkerNodes = ["10250", "10256", "30000:32767"];
var K8sStack = class extends import_aws_cdk_lib.Stack {
constructor(scope, id, clusterProps, stackProps) {
super(scope, id, stackProps);
this.workerInstances = [];
this.subnets = [];
this.clusterProps = clusterProps;
this.validateAttributes();
this.vpc = this.getVpc();
this.setSubnets();
const clusterName = this.clusterProps.clusterName ?? "k8s";
this.ctrlPlaneInstanceSg = this.createSecurityGroup(
`${clusterName}-ctrl-plane-sg`,
"SG for K8 Control Planen instance"
);
this.workerSecurityGroup = this.createSecurityGroup(
`${clusterName}-worker-node-sg`,
"SG for Worker Node instance"
);
this.setInboundRules("ControlPlane");
this.setInboundRules("Worker");
this.ec2Role = this.getInstanceRole();
this.ec2KeyPair = clusterProps.keyPairName ? import_aws_ec2.KeyPair.fromKeyPairName(this, "key-pair", clusterProps.keyPairName) : void 0;
this.createWorkerInstances(clusterName, this.clusterProps.workerNodesCount);
this.createControlPlaneInstance(clusterName);
this.setOutput(clusterName);
}
validateAttributes() {
let errorMessage = void 0;
if (this.clusterProps.subnets && this.clusterProps.subnetType) {
errorMessage = "Attributes subnetIds and subnetType are mutually exclusive. Please remove one of the attributes from K8sClusterProps";
}
this.validateIngressRules(
"ControlPlane",
this.clusterProps?.controlPlaneInstance?.ingressRules
);
this.validateIngressRules(
"Worker",
this.clusterProps?.workerInstance?.ingressRules
);
if (errorMessage) throw new Error(errorMessage);
}
validateIngressRules(nodeType, ingressRules = []) {
ingressRules.forEach((rule) => {
if (rule.peerType === "SecurityGroup" && !rule.peer) {
throw new Error(
`attribute 'peer'is mandatory for an ingress rule when 'peerType' is defined as 'SecurityGroup' for ${nodeType} node`
);
}
});
}
createWorkerInstances(clusterName, workerNodesCount = 1) {
for (let i = 0; i < workerNodesCount; i++) {
this.workerInstances.push(
this.createInstance(
`${clusterName}-worker-${i + 1}`,
this.workerSecurityGroup,
k8sUserData,
this.clusterProps.workerInstance
)
);
}
}
createControlPlaneInstance(clusterName) {
const joinWorkersUserData = this.workerInstances.map(
(workerInstance) => `aws ssm send-command --instance-ids "${workerInstance.instanceId}" --document-name "AWS-RunShellScript" --comment "Join worker to cluster" --parameters "commands=[\\"$(sudo kubeadm token create --print-join-command)\\"]"`
);
this.ctrlPlaneInstance = this.createInstance(
`${clusterName}-ctrl-plane`,
this.ctrlPlaneInstanceSg,
[...k8sUserData, ...controlPlaneUserData, ...joinWorkersUserData],
this.clusterProps.controlPlaneInstance
);
this.workerInstances.forEach(
(instance) => this.ctrlPlaneInstance.node.addDependency(instance)
);
}
getVpc() {
return import_aws_ec2.Vpc.fromLookup(this, "vpc", {
vpcId: this.clusterProps.vpcId
});
}
setSubnets() {
this.subnets = (this.clusterProps.subnets || []).map(
(subnet) => import_aws_ec2.Subnet.fromSubnetAttributes(this, `subnet-${subnet.subnetId}`, {
subnetId: subnet.subnetId,
availabilityZone: subnet.availabilityZone
})
);
}
createSecurityGroup(id, description) {
const sg = new import_aws_ec2.SecurityGroup(this, id, {
vpc: this.vpc,
description,
securityGroupName: this.getName(id)
});
if (this.clusterProps.associatePublicIpAddress === true)
this.addPublicIpv4IngressForSessionManager(sg);
return sg;
}
getInstanceRole() {
const managedPolicies = [
import_aws_iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
import_aws_iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonEC2FullAccess")
];
const ssmPolicy = new import_aws_iam.PolicyStatement({
sid: "SendCommand",
actions: ["ssm:SendCommand"],
resources: ["*"]
});
if (!this.clusterProps.roleArn)
return this.createEc2Role("ec2-role", managedPolicies, ssmPolicy);
else {
const role = import_aws_iam.Role.fromRoleArn(
this,
"ec2-role",
this.clusterProps.roleArn
);
managedPolicies.forEach(
(managedPolicy) => role.addManagedPolicy(managedPolicy)
);
role.addToPrincipalPolicy(ssmPolicy);
return role;
}
}
createEc2Role(roleName, managedPolicies, ssmPolicy) {
return new import_aws_iam.Role(this, roleName, {
assumedBy: new import_aws_iam.ServicePrincipal("ec2.amazonaws.com"),
description: "Role for EC2 instance",
managedPolicies,
inlinePolicies: {
SsmPolicy: new import_aws_iam.PolicyDocument({
statements: [ssmPolicy]
})
},
roleName: this.getName(roleName)
});
}
addPublicIpv4IngressForSessionManager(sg) {
if (this.clusterProps.associatePublicIpAddress === true) {
sg.addIngressRule(
import_aws_ec2.Peer.anyIpv4(),
import_aws_ec2.Port.HTTPS,
"allow from local to connect"
);
}
}
setInboundRules(nodeType) {
const sg = nodeType == "ControlPlane" ? this.ctrlPlaneInstanceSg : this.workerSecurityGroup;
const sourceSg = nodeType == "ControlPlane" ? this.workerSecurityGroup : this.ctrlPlaneInstanceSg;
const portsToOpen = nodeType == "ControlPlane" ? portsToOpenForWorkerNodesInCtrlPlane : portsToOpenForCtrlPlaneInWorkerNodes;
portsToOpen.forEach((port) => {
const portSplit = port.split(":").map((portValue) => parseInt(portValue));
const connection = portSplit.length == 1 ? import_aws_ec2.Port.tcp(portSplit[0]) : import_aws_ec2.Port.tcpRange(portSplit[0], portSplit[1]);
sg.addIngressRule(sourceSg, connection);
this.addIngressRules(
sg,
nodeType,
this.clusterProps.controlPlaneInstance?.ingressRules
);
});
}
addIngressRules(sg, nodeType, ingressRules = []) {
ingressRules.forEach((ingressRule, index) => {
sg.addIngressRule(
ingressRule.peerType === "SecurityGroup" ? import_aws_ec2.SecurityGroup.fromSecurityGroupId(
this,
`${nodeType}-sg-${index}`,
ingressRule.peer
) : import_aws_ec2.Peer.anyIpv4(),
ingressRule.port.upperRange ? import_aws_ec2.Port.tcpRange(
ingressRule.port.lowerRange,
ingressRule.port.upperRange
) : import_aws_ec2.Port.tcp(ingressRule.port.lowerRange)
);
});
}
getVolume(volumeProps) {
return {
deviceName: volumeProps.deviceName,
volume: import_aws_ec2.BlockDeviceVolume.ebs(volumeProps.volumeSizeinGb ?? 20),
volumeType: volumeProps.volumeType ?? import_aws_ec2.EbsDeviceVolumeType.GENERAL_PURPOSE_SSD_GP3,
deleteOnTermination: volumeProps.deleteOnTermination ?? true
};
}
createInstance(instanceName, sg, k8sUserData2, instanceProps = {}) {
const primaryVolume = this.getVolume({
...instanceProps.primaryVolume,
deviceName: "/dev/xvda"
});
const secondaryVolumes = (instanceProps.secondaryVolumes || []).map(
(volumeProps) => this.getVolume(volumeProps)
);
secondaryVolumes?.forEach((volumeProps) => {
if (volumeProps?.deviceName == primaryVolume.deviceName)
throw new Error(
`devicename can not be same for primary and secondary volumes for instance ${instanceName}`
);
});
const blockDevices = [primaryVolume, ...secondaryVolumes];
const userData = import_aws_ec2.UserData.forLinux();
const userDataCommands = (instanceProps.prependUserData || []).concat(k8sUserData2).concat(instanceProps.appendUserData || []);
userData.addCommands(...userDataCommands);
return new import_aws_ec2.Instance(this, instanceName, {
instanceProfile: new import_aws_iam.InstanceProfile(this, `${instanceName}-profile`, {
instanceProfileName: `${instanceName}-profile`,
role: this.ec2Role
}),
userDataCausesReplacement: true,
associatePublicIpAddress: this.clusterProps.associatePublicIpAddress,
vpcSubnets: {
subnetType: this.subnets.length == 0 && this.clusterProps.subnetType === void 0 ? import_aws_ec2.SubnetType.PUBLIC : this.clusterProps.subnetType,
subnets: this.subnets.length > 0 ? this.subnets : void 0
},
instanceType: import_aws_ec2.InstanceType.of(
instanceProps.type ?? import_aws_ec2.InstanceClass.T4G,
instanceProps.size ?? import_aws_ec2.InstanceSize.MEDIUM
),
keyPair: this.ec2KeyPair,
machineImage: import_aws_ec2.MachineImage.fromSsmParameter(
this.clusterProps.amiParamName
),
blockDevices,
requireImdsv2: true,
vpc: this.vpc,
securityGroup: sg,
userData,
instanceName: this.getName(instanceName)
});
}
setOutput(clusterName) {
new import_aws_cdk_lib.CfnOutput(this, "output-ctrl", {
key: "CtrlPlaneInstanceId",
value: this.ctrlPlaneInstance.instanceId
});
this.workerInstances.forEach((instance, index) => {
new import_aws_cdk_lib.CfnOutput(this, `output-w${index + 1}`, {
key: `Worker${index + 1}InstanceId`,
value: instance.instanceId
});
});
new import_aws_cdk_lib.CfnOutput(this, "ctrl-plane-sg-output", {
key: "CtrlPlaneSecurityGroup",
value: this.ctrlPlaneInstanceSg.securityGroupId,
exportName: this.getProperCase(`${clusterName}-CtrlPlaneSecurityGroup`)
});
new import_aws_cdk_lib.CfnOutput(this, "worker-node-sg-output", {
key: "WorkerNodeSecurityGroup",
value: this.workerSecurityGroup.securityGroupId,
exportName: this.getProperCase(`${clusterName}-WorkerNodeSecurityGroup`)
});
}
getProperCase(id) {
const delimiter = id.includes("-") ? "-" : "_";
return id.split(delimiter).map((word) => {
word = word.toLowerCase();
return word[0].toUpperCase() + word.slice(1);
}).join("");
}
getName(name) {
const namePrefix = this.clusterProps.namePrefix ? `${this.clusterProps.namePrefix}-` : "";
const envTag = this.clusterProps.envTag ? `-${this.clusterProps.envTag}` : "";
return `${namePrefix}${name}${envTag}`;
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
K8sStack
});
//# sourceMappingURL=index.js.map