-
-
Save nsisodiya/e1ba779fd5ed6c73d00446c0379ab95b to your computer and use it in GitHub Desktop.
sample k8s cluseter using pulumi : node.js micro-service 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 aws from '@pulumi/aws'; | |
import * as awsx from '@pulumi/awsx'; | |
import * as eks from '@pulumi/eks'; | |
import * as k8s from '@pulumi/kubernetes'; | |
import * as helm from '@pulumi/kubernetes/helm'; | |
import * as pulumi from '@pulumi/pulumi'; | |
import * as dotenv from 'dotenv'; | |
dotenv.config({ path: '.env' }); | |
const clusterName = 'syngenta-platform'; | |
const env = pulumi.getStack(); | |
const vpc = new awsx.ec2.Vpc(clusterName, { | |
subnets: [ | |
{ | |
type: 'public', | |
tags: { | |
'kubernetes.io/role/elb': '', | |
'kubernetes.io/cluster/syngenta-platform-eksCluster-128eacd': 'shared' // TODO, WTF | |
} | |
} | |
] | |
}); | |
// ========================= Making new Cluser ======================= | |
const synPlatformCluster = new eks.Cluster(clusterName, { | |
vpcId: vpc.id, | |
subnetIds: vpc.publicSubnetIds, | |
desiredCapacity: 2, | |
minSize: 1, | |
maxSize: 2, | |
storageClasses: 'gp2', | |
deployDashboard: false, | |
vpcCniOptions: { | |
warmIpTarget: 4 | |
} | |
}); | |
// Export the clusters' kubeconfig. | |
export const synPlatformClusterName = synPlatformCluster.eksCluster.name; | |
export const kubeconfig = synPlatformCluster.kubeconfig; | |
export const clusterNodeInstanceRoleName = synPlatformCluster.instanceRoles.apply((roles) => roles[0].name); | |
export const nodesubnetId = synPlatformCluster.core.subnetIds; | |
// Create a Kubernetes Namespace for Syngenta platform | |
const synPlatformNS = new k8s.core.v1.Namespace( | |
clusterName, | |
{ metadata: { name: clusterName } }, | |
{ provider: synPlatformCluster.provider } | |
); | |
// Create a Kubernetes Namespace for AgroMaster | |
const agromasterNS = new k8s.core.v1.Namespace( | |
'agromaster', | |
{ metadata: { name: 'agromaster' } }, | |
{ provider: synPlatformCluster.provider } | |
); | |
interface IMSConfig { | |
product: string; | |
microService: string; | |
imageUrl: string | pulumi.Output<string>; | |
containerPort: number; | |
targetPort: number; | |
} | |
// https://github.com/pulumi/examples/blob/9d9fc3d18997e279acd5c86d8f725ed9914278c8/kubernetes-ts-sock-shop/index.ts | |
// ====================== Utility functions ========================== | |
const baseTags = { | |
ManagedBy: 'Pulumi' | |
}; | |
interface ICreateRepoArg { | |
product: string; | |
microService: string; | |
} | |
function createECRRepo(config: ICreateRepoArg) { | |
const { product, microService } = config; | |
const repoName = `${product}-${microService}`; | |
const repo = new aws.ecr.Repository(repoName, { | |
name: repoName, | |
imageScanningConfiguration: { | |
scanOnPush: false // TODO enable later. | |
}, | |
imageTagMutability: 'MUTABLE', | |
tags: { | |
...baseTags, | |
Product: product, | |
Environment: 'production' | |
} | |
}); | |
const repopolicy = new aws.ecr.LifecyclePolicy(`${repoName}-policy`, { | |
policy: `{ | |
"rules": [ | |
{ | |
"rulePriority": 1, | |
"description": "Keep last 5 images", | |
"selection": { | |
"tagStatus": "untagged", | |
"countType": "imageCountMoreThan", | |
"countNumber": 5 | |
}, | |
"action": { | |
"type": "expire" | |
} | |
} | |
] | |
} | |
`, | |
repository: repo.name | |
}); | |
return repo.repositoryUrl; | |
} | |
function createMicroService(config: IMSConfig, namespace: any, cluster: any) { | |
const { product, microService, imageUrl, containerPort, targetPort } = config; | |
// const appLabels = { appClass: clusterName }; | |
const uniqueServiceName = `${product}-${microService}`; | |
const urlPattern = `${product}-api.syn-platform.com/${microService}`; | |
const urlPattern1 = `${uniqueServiceName}-api.syn-platform.com`; | |
const deployment = new k8s.apps.v1.Deployment( | |
uniqueServiceName, | |
{ | |
metadata: { | |
namespace: namespace.metadata.name, | |
name: uniqueServiceName, | |
labels: { | |
name: uniqueServiceName | |
} | |
}, | |
spec: { | |
replicas: 1, | |
selector: { | |
matchLabels: { | |
name: uniqueServiceName | |
} | |
}, | |
template: { | |
metadata: { | |
labels: { | |
name: uniqueServiceName | |
} | |
}, | |
spec: { | |
containers: [ | |
{ | |
name: uniqueServiceName, | |
image: imageUrl, | |
imagePullPolicy: 'Always', | |
ports: [{ name: 'http', containerPort }] | |
} | |
] | |
} | |
} | |
} | |
}, | |
{ | |
provider: cluster.provider | |
} | |
); | |
// Create a LoadBalancer Service for the NGINX Deployment | |
const service = new k8s.core.v1.Service( | |
uniqueServiceName, | |
{ | |
metadata: { | |
name: uniqueServiceName, | |
labels: { | |
name: uniqueServiceName | |
}, | |
namespace: namespace.metadata.name | |
}, | |
spec: { | |
type: 'NodePort', | |
ports: [{ port: 80, protocol: 'TCP', targetPort }], | |
selector: { | |
name: uniqueServiceName | |
} | |
} | |
}, | |
{ | |
provider: cluster.provider | |
} | |
); | |
return { | |
deployment, | |
service | |
}; | |
} | |
// ====================== NGNIX START ================ | |
const syngentaPlatformNgnix = createMicroService( | |
{ | |
product: 'syngenta-platform', | |
microService: 'nginx', | |
imageUrl: 'nginx:latest', | |
containerPort: 80, | |
targetPort: 80 | |
}, | |
synPlatformNS, | |
synPlatformCluster | |
); | |
// ====================== NDVI Service START ================ | |
// https://www.pulumi.com/docs/aws/ecr/ | |
const syngentaPlatformNdviECRUrl = createECRRepo({ | |
product: 'syngenta-platform', | |
microService: 'ndvi' | |
}); | |
const syngentaPlatformNdvi = createMicroService( | |
{ | |
product: 'syngenta-platform', | |
microService: 'ndvi', | |
imageUrl: syngentaPlatformNdviECRUrl, | |
containerPort: 3000, | |
targetPort: 80 | |
}, | |
synPlatformNS, | |
synPlatformCluster | |
); | |
// ==================== AgroMaster Plot Service START ========== | |
const agroMasterPlotECRUrl = createECRRepo({ | |
product: 'agromaster', | |
microService: 'plot' | |
}); | |
const agroMasterPlot = createMicroService( | |
{ | |
product: 'agromaster', | |
microService: 'plot', | |
imageUrl: agroMasterPlotECRUrl, | |
containerPort: 3000, | |
targetPort: 80 | |
}, | |
agromasterNS, | |
synPlatformCluster | |
); | |
// STEP 3: Declare the AWS ALB Ingress Controller | |
// Create IAM Policy for the IngressController called "ingressController-iam-policy” and read the policy ARN. | |
const ingressControllerPolicy = new aws.iam.Policy('ingressController-iam-policy', { | |
policy: { | |
Version: '2012-10-17', | |
Statement: [ | |
{ | |
Effect: 'Allow', | |
Action: ['acm:DescribeCertificate', 'acm:ListCertificates', 'acm:GetCertificate'], | |
Resource: '*' | |
}, | |
{ | |
Effect: 'Allow', | |
Action: [ | |
'ec2:AuthorizeSecurityGroupIngress', | |
'ec2:CreateSecurityGroup', | |
'ec2:CreateTags', | |
'ec2:DeleteTags', | |
'ec2:DeleteSecurityGroup', | |
'ec2:DescribeInstances', | |
'ec2:DescribeInstanceStatus', | |
'ec2:DescribeSecurityGroups', | |
'ec2:DescribeSubnets', | |
'ec2:DescribeTags', | |
'ec2:DescribeVpcs', | |
'ec2:ModifyInstanceAttribute', | |
'ec2:ModifyNetworkInterfaceAttribute', | |
'ec2:RevokeSecurityGroupIngress' | |
], | |
Resource: '*' | |
}, | |
{ | |
Effect: 'Allow', | |
Action: [ | |
'elasticloadbalancing:AddTags', | |
'elasticloadbalancing:CreateListener', | |
'elasticloadbalancing:CreateLoadBalancer', | |
'elasticloadbalancing:CreateRule', | |
'elasticloadbalancing:CreateTargetGroup', | |
'elasticloadbalancing:DeleteListener', | |
'elasticloadbalancing:DeleteLoadBalancer', | |
'elasticloadbalancing:DeleteRule', | |
'elasticloadbalancing:DeleteTargetGroup', | |
'elasticloadbalancing:DeregisterTargets', | |
'elasticloadbalancing:DescribeListeners', | |
'elasticloadbalancing:DescribeLoadBalancers', | |
'elasticloadbalancing:DescribeLoadBalancerAttributes', | |
'elasticloadbalancing:DescribeRules', | |
'elasticloadbalancing:DescribeSSLPolicies', | |
'elasticloadbalancing:DescribeTags', | |
'elasticloadbalancing:DescribeTargetGroups', | |
'elasticloadbalancing:DescribeTargetGroupAttributes', | |
'elasticloadbalancing:DescribeTargetHealth', | |
'elasticloadbalancing:ModifyListener', | |
'elasticloadbalancing:ModifyLoadBalancerAttributes', | |
'elasticloadbalancing:ModifyRule', | |
'elasticloadbalancing:ModifyTargetGroup', | |
'elasticloadbalancing:ModifyTargetGroupAttributes', | |
'elasticloadbalancing:RegisterTargets', | |
'elasticloadbalancing:RemoveTags', | |
'elasticloadbalancing:SetIpAddressType', | |
'elasticloadbalancing:SetSecurityGroups', | |
'elasticloadbalancing:SetSubnets', | |
'elasticloadbalancing:SetWebACL' | |
], | |
Resource: '*' | |
}, | |
{ | |
Effect: 'Allow', | |
Action: ['iam:GetServerCertificate', 'iam:ListServerCertificates'], | |
Resource: '*' | |
}, | |
{ | |
Effect: 'Allow', | |
Action: [ | |
'waf-regional:GetWebACLForResource', | |
'waf-regional:GetWebACL', | |
'waf-regional:AssociateWebACL', | |
'waf-regional:DisassociateWebACL' | |
], | |
Resource: '*' | |
}, | |
{ | |
Effect: 'Allow', | |
Action: ['tag:GetResources', 'tag:TagResources'], | |
Resource: '*' | |
}, | |
{ | |
Effect: 'Allow', | |
Action: ['waf:GetWebACL'], | |
Resource: '*' | |
} | |
] | |
} | |
}); | |
// Attach this policy to the NodeInstanceRole of the worker nodes. | |
export const nodeinstanceRole = new aws.iam.RolePolicyAttachment('eks-NodeInstanceRole-policy-attach', { | |
policyArn: ingressControllerPolicy.arn, | |
role: clusterNodeInstanceRoleName | |
}); | |
// Declare the ALBIngressController in 1 step with the Helm Chart. | |
const albingresscntlr = new k8s.helm.v2.Chart( | |
'alb', | |
{ | |
chart: 'http://storage.googleapis.com/kubernetes-charts-incubator/aws-alb-ingress-controller-0.1.9.tgz', | |
values: { | |
clusterName: synPlatformClusterName, | |
autoDiscoverAwsRegion: 'true', | |
autoDiscoverAwsVpcID: 'true' | |
} | |
}, | |
{ provider: synPlatformCluster.provider } | |
); | |
// =================== Create Ingress =========== | |
const router = new k8s.networking.v1beta1.Ingress( | |
'syngenta-platform', | |
{ | |
metadata: { | |
name: 'syngenta-platform', | |
labels: { | |
app: 'syngenta-platform' | |
}, | |
namespace: synPlatformNS.metadata.name, | |
annotations: { | |
'alb.ingress.kubernetes.io/healthcheck-protocol': 'HTTP', // TODO it should be https, I guesss | |
'alb.ingress.kubernetes.io/success-codes': '200', | |
'alb.ingress.kubernetes.io/healthcheck-interval-seconds': '10', | |
'alb.ingress.kubernetes.io/healthcheck-path': '/health', | |
'alb.ingress.kubernetes.io/healthcheck-port': '80', | |
'kubernetes.io/ingress.class': 'alb', | |
'alb.ingress.kubernetes.io/scheme': 'internet-facing' | |
} | |
}, | |
spec: { | |
rules: [ | |
{ | |
// host: 'api.syn-platform.com', // TODO what is this? | |
http: { | |
paths: [ | |
{ | |
path: '/ndvi/*', | |
backend: { | |
serviceName: syngentaPlatformNdvi.service.metadata.name, | |
servicePort: 80 | |
} | |
}, | |
{ | |
path: '/ngnix/*', | |
backend: { | |
serviceName: syngentaPlatformNgnix.service.metadata.name, | |
servicePort: 80 | |
} | |
} | |
] | |
} | |
} | |
] | |
} | |
}, | |
{ | |
provider: synPlatformCluster.provider | |
} | |
); | |
// export const ingressUrl = router; | |
/* | |
// Split a domain name into its subdomain and parent domain names. | |
// e.g. "www.example.com" => "www", "example.com". | |
function getDomainAndSubdomain(domain: string): { subdomain: string; parentDomain: string } { | |
const parts = domain.split('.'); | |
if (parts.length < 2) { | |
throw new Error(`No TLD found on ${domain}`); | |
} | |
// No subdomain, e.g. awesome-website.com. | |
if (parts.length === 2) { | |
return { subdomain: '', parentDomain: domain }; | |
} | |
const subdomain = parts[0]; | |
parts.shift(); // Drop first element. | |
return { | |
subdomain, | |
// Trailing "." to canonicalize domain. | |
parentDomain: parts.join('.') + '.' | |
}; | |
} | |
function createAliasRecord(targetDomain: string, url: aws.cloudfront.Distribution): aws.route53.Record { | |
const domainParts = getDomainAndSubdomain(targetDomain); | |
console.log('domainParts', domainParts); | |
const hostedZoneId = aws.route53 | |
.getZone({ name: domainParts.parentDomain }, { async: true }) | |
.then((zone) => zone.zoneId); | |
console.log('hostedZoneId', hostedZoneId); | |
return new aws.route53.Record(targetDomain, { | |
name: domainParts.subdomain, | |
zoneId: hostedZoneId, | |
type: 'A', | |
aliases: [ | |
{ | |
name: distribution.domainName, | |
zoneId: distribution.hostedZoneId, | |
evaluateTargetHealth: false | |
} | |
] | |
}); | |
} | |
const aRecord = createAliasRecord('api.syn-platform.com', cdn); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment