Skip to content

Instantly share code, notes, and snippets.

@nsisodiya
Last active January 15, 2020 09:07
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 nsisodiya/e1ba779fd5ed6c73d00446c0379ab95b to your computer and use it in GitHub Desktop.
Save nsisodiya/e1ba779fd5ed6c73d00446c0379ab95b to your computer and use it in GitHub Desktop.
sample k8s cluseter using pulumi : node.js micro-service using Pulumi
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