| 
 | 1 | +// Copyright 2016-2025, Pulumi Corporation.  All rights reserved.  | 
 | 2 | + | 
 | 3 | +import * as aws from "@pulumi/aws";  | 
 | 4 | +import * as awsx from "@pulumi/awsx";  | 
 | 5 | +import * as pulumi from "@pulumi/pulumi";  | 
 | 6 | + | 
 | 7 | + | 
 | 8 | +const config = new pulumi.Config();  | 
 | 9 | +const projectName = "zookeeper";  | 
 | 10 | +const environment = config.require("environment");  | 
 | 11 | + | 
 | 12 | +// VPC Configuration  | 
 | 13 | +const vpc = new awsx.ec2.Vpc(`${projectName}-vpc`, {  | 
 | 14 | +  numberOfAvailabilityZones: 3,  | 
 | 15 | +  natGateways: {  | 
 | 16 | +    strategy: environment === "production" ? "OnePerAz" : "Single",  | 
 | 17 | +  },  | 
 | 18 | +  tags: {  | 
 | 19 | +    Name: `${projectName}-vpc`,  | 
 | 20 | +    Environment: environment,  | 
 | 21 | +  },  | 
 | 22 | +});  | 
 | 23 | + | 
 | 24 | +// Security Groups  | 
 | 25 | +const zookeeperSG = new aws.ec2.SecurityGroup(`${projectName}-sg`, {  | 
 | 26 | +  vpcId: vpc.vpcId,  | 
 | 27 | +  description: "Security group for ZooKeeper nodes",  | 
 | 28 | +  ingress: [  | 
 | 29 | +    { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["10.0.0.0/8"] },  // SSH  | 
 | 30 | +    { protocol: "tcp", fromPort: 2181, toPort: 2181, cidrBlocks: ["10.0.0.0/8"] },  // Client port  | 
 | 31 | +    { protocol: "tcp", fromPort: 2888, toPort: 2888, cidrBlocks: ["10.0.0.0/8"] },  // Follower port  | 
 | 32 | +    { protocol: "tcp", fromPort: 3888, toPort: 3888, cidrBlocks: ["10.0.0.0/8"] },  // Election port  | 
 | 33 | +  ],  | 
 | 34 | +  egress: [{  | 
 | 35 | +    protocol: "-1",  | 
 | 36 | +    fromPort: 0,  | 
 | 37 | +    toPort: 0,  | 
 | 38 | +    cidrBlocks: ["0.0.0.0/0"],  | 
 | 39 | +  }],  | 
 | 40 | +  tags: {  | 
 | 41 | +    Name: `${projectName}-sg`,  | 
 | 42 | +    Environment: environment,  | 
 | 43 | +  },  | 
 | 44 | +});  | 
 | 45 | + | 
 | 46 | +// IAM Role and Instance Profile  | 
 | 47 | +const zookeeperRole = new aws.iam.Role(`${projectName}-role`, {  | 
 | 48 | +  assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({  | 
 | 49 | +    Service: "ec2.amazonaws.com",  | 
 | 50 | +  }),  | 
 | 51 | +});  | 
 | 52 | + | 
 | 53 | +const zookeeperInstanceProfile = new aws.iam.InstanceProfile(`${projectName}-instance-profile`, {  | 
 | 54 | +  role: zookeeperRole.name,  | 
 | 55 | +});  | 
 | 56 | + | 
 | 57 | +// SSM Policy Attachment  | 
 | 58 | +new aws.iam.RolePolicyAttachment(`${projectName}-ssm-policy`, {  | 
 | 59 | +  role: zookeeperRole.name,  | 
 | 60 | +  policyArn: "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",  | 
 | 61 | +});  | 
 | 62 | + | 
 | 63 | +// CloudWatch Policy  | 
 | 64 | +const cloudwatchPolicy = new aws.iam.RolePolicy(`${projectName}-cloudwatch-policy`, {  | 
 | 65 | +  role: zookeeperRole.name,  | 
 | 66 | +  policy: JSON.stringify({  | 
 | 67 | +    Version: "2012-10-17",  | 
 | 68 | +    Statement: [{  | 
 | 69 | +      Effect: "Allow",  | 
 | 70 | +      Action: [  | 
 | 71 | +        "cloudwatch:PutMetricData",  | 
 | 72 | +        "cloudwatch:GetMetricData",  | 
 | 73 | +        "cloudwatch:ListMetrics",  | 
 | 74 | +      ],  | 
 | 75 | +      Resource: "*",  | 
 | 76 | +    }],  | 
 | 77 | +  }),  | 
 | 78 | +});  | 
 | 79 | + | 
 | 80 | +// Launch Template User Data Script  | 
 | 81 | +const getUserData = (id: number) => `#!/bin/bash  | 
 | 82 | +apt-get update  | 
 | 83 | +apt-get install -y openjdk-11-jdk  | 
 | 84 | +
  | 
 | 85 | +# Install ZooKeeper  | 
 | 86 | +ZOOKEEPER_VERSION="3.9.3"  | 
 | 87 | +wget https://dlcdn.apache.org/zookeeper/zookeeper-$ZOOKEEPER_VERSION/apache-zookeeper-$ZOOKEEPER_VERSION-bin.tar.gz  | 
 | 88 | +tar -xzf apache-zookeeper-$ZOOKEEPER_VERSION-bin.tar.gz  | 
 | 89 | +mv apache-zookeeper-$ZOOKEEPER_VERSION-bin /opt/zookeeper  | 
 | 90 | +
  | 
 | 91 | +# Configure ZooKeeper  | 
 | 92 | +cat > /opt/zookeeper/conf/zoo.cfg << EOF  | 
 | 93 | +tickTime=2000  | 
 | 94 | +initLimit=10  | 
 | 95 | +syncLimit=5  | 
 | 96 | +dataDir=/var/lib/zookeeper  | 
 | 97 | +clientPort=2181  | 
 | 98 | +server.1=zk1.internal:2888:3888  | 
 | 99 | +server.2=zk2.internal:2888:3888  | 
 | 100 | +server.3=zk3.internal:2888:3888  | 
 | 101 | +EOF  | 
 | 102 | +
  | 
 | 103 | +mkdir -p /var/lib/zookeeper  | 
 | 104 | +echo "${id}" > /var/lib/zookeeper/myid  | 
 | 105 | +
  | 
 | 106 | +# Create systemd service  | 
 | 107 | +cat > /etc/systemd/system/zookeeper.service << EOF  | 
 | 108 | +[Unit]  | 
 | 109 | +Description=ZooKeeper Service  | 
 | 110 | +After=network.target  | 
 | 111 | +
  | 
 | 112 | +[Service]  | 
 | 113 | +Type=forking  | 
 | 114 | +User=root  | 
 | 115 | +Group=root  | 
 | 116 | +ExecStart=/opt/zookeeper/bin/zkServer.sh start  | 
 | 117 | +ExecStop=/opt/zookeeper/bin/zkServer.sh stop  | 
 | 118 | +ExecReload=/opt/zookeeper/bin/zkServer.sh restart  | 
 | 119 | +WorkingDirectory=/opt/zookeeper  | 
 | 120 | +
  | 
 | 121 | +[Install]  | 
 | 122 | +WantedBy=multi-user.target  | 
 | 123 | +EOF  | 
 | 124 | +
  | 
 | 125 | +# Start ZooKeeper  | 
 | 126 | +systemctl daemon-reload  | 
 | 127 | +systemctl enable zookeeper  | 
 | 128 | +systemctl start zookeeper  | 
 | 129 | +
  | 
 | 130 | +# Install CloudWatch agent  | 
 | 131 | +wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb  | 
 | 132 | +dpkg -i amazon-cloudwatch-agent.deb  | 
 | 133 | +
  | 
 | 134 | +# Configure CloudWatch agent  | 
 | 135 | +cat > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json << EOF  | 
 | 136 | +{  | 
 | 137 | +    "metrics": {  | 
 | 138 | +        "metrics_collected": {  | 
 | 139 | +            "mem": {  | 
 | 140 | +                "measurement": ["mem_used_percent"]  | 
 | 141 | +            },  | 
 | 142 | +            "disk": {  | 
 | 143 | +                "measurement": ["disk_used_percent"],  | 
 | 144 | +                "resources": ["/"]  | 
 | 145 | +            }  | 
 | 146 | +        }  | 
 | 147 | +    }  | 
 | 148 | +}  | 
 | 149 | +EOF  | 
 | 150 | +
  | 
 | 151 | +systemctl enable amazon-cloudwatch-agent  | 
 | 152 | +systemctl start amazon-cloudwatch-agent`;  | 
 | 153 | + | 
 | 154 | +// Launch Template  | 
 | 155 | +const launchTemplate = new aws.ec2.LaunchTemplate(`${projectName}-launch-template`, {  | 
 | 156 | +  namePrefix: `${projectName}-`,  | 
 | 157 | +  imageId: "ami-0c7217cdde317cfec",  // Ubuntu 24.04 LTS AMI ID  | 
 | 158 | +  instanceType: "t3.medium",  | 
 | 159 | +  userData: Buffer.from(getUserData(1)).toString("base64"),  | 
 | 160 | +  vpcSecurityGroupIds: [zookeeperSG.id],  | 
 | 161 | +  iamInstanceProfile: {  | 
 | 162 | +    name: zookeeperInstanceProfile.name,  | 
 | 163 | +  },  | 
 | 164 | +  blockDeviceMappings: [{  | 
 | 165 | +    deviceName: "/dev/sda1",  | 
 | 166 | +    ebs: {  | 
 | 167 | +      volumeSize: 50,  | 
 | 168 | +      volumeType: "gp3",  | 
 | 169 | +      deleteOnTermination: "true",  | 
 | 170 | +      encrypted: "true",  | 
 | 171 | +    },  | 
 | 172 | +  }],  | 
 | 173 | +  tags: {  | 
 | 174 | +    Name: `${projectName}-launch-template`,  | 
 | 175 | +    Environment: environment,  | 
 | 176 | +  },  | 
 | 177 | +  metadataOptions: {  | 
 | 178 | +    httpEndpoint: "enabled",  | 
 | 179 | +    httpTokens: "required",  | 
 | 180 | +    httpPutResponseHopLimit: 1,  | 
 | 181 | +  },  | 
 | 182 | +});  | 
 | 183 | + | 
 | 184 | +// Auto Scaling Group  | 
 | 185 | +const asg = new aws.autoscaling.Group(`${projectName}-asg`, {  | 
 | 186 | +  vpcZoneIdentifiers: vpc.privateSubnetIds,  | 
 | 187 | +  desiredCapacity: 3,  | 
 | 188 | +  maxSize: 3,  | 
 | 189 | +  minSize: 3,  | 
 | 190 | +  healthCheckType: "ELB",  | 
 | 191 | +  healthCheckGracePeriod: 300,  | 
 | 192 | +  launchTemplate: {  | 
 | 193 | +    id: launchTemplate.id,  | 
 | 194 | +    version: "$Latest",  | 
 | 195 | +  },  | 
 | 196 | +  tags: [{  | 
 | 197 | +    key: "Name",  | 
 | 198 | +    value: `${projectName}-node`,  | 
 | 199 | +    propagateAtLaunch: true,  | 
 | 200 | +  }, {  | 
 | 201 | +    key: "Environment",  | 
 | 202 | +    value: environment,  | 
 | 203 | +    propagateAtLaunch: true,  | 
 | 204 | +  }],  | 
 | 205 | +});  | 
 | 206 | + | 
 | 207 | +// Application Load Balancer  | 
 | 208 | +const alb = new aws.lb.LoadBalancer(`${projectName}-alb`, {  | 
 | 209 | +  internal: true,  | 
 | 210 | +  loadBalancerType: "application",  | 
 | 211 | +  securityGroups: [zookeeperSG.id],  | 
 | 212 | +  subnets: vpc.privateSubnetIds,  | 
 | 213 | +  tags: {  | 
 | 214 | +    Name: `${projectName}-alb`,  | 
 | 215 | +    Environment: environment,  | 
 | 216 | +  },  | 
 | 217 | +});  | 
 | 218 | + | 
 | 219 | +// Target Group  | 
 | 220 | +const targetGroup = new aws.lb.TargetGroup(`${projectName}-tg`, {  | 
 | 221 | +  port: 2181,  | 
 | 222 | +  protocol: "HTTP",  | 
 | 223 | +  vpcId: vpc.vpcId,  | 
 | 224 | +  targetType: "instance",  | 
 | 225 | +  healthCheck: {  | 
 | 226 | +    enabled: true,  | 
 | 227 | +    path: "/",  | 
 | 228 | +    port: "2181",  | 
 | 229 | +    protocol: "HTTP",  | 
 | 230 | +    healthyThreshold: 2,  | 
 | 231 | +    unhealthyThreshold: 3,  | 
 | 232 | +    timeout: 5,  | 
 | 233 | +    interval: 30,  | 
 | 234 | +  },  | 
 | 235 | +  tags: {  | 
 | 236 | +    Name: `${projectName}-tg`,  | 
 | 237 | +    Environment: environment,  | 
 | 238 | +  },  | 
 | 239 | +});  | 
 | 240 | + | 
 | 241 | +// ALB Listener  | 
 | 242 | +const listener = new aws.lb.Listener(`${projectName}-listener`, {  | 
 | 243 | +  loadBalancerArn: alb.arn,  | 
 | 244 | +  port: 2181,  | 
 | 245 | +  protocol: "HTTP",  | 
 | 246 | +  defaultActions: [{  | 
 | 247 | +    type: "forward",  | 
 | 248 | +    targetGroupArn: targetGroup.arn,  | 
 | 249 | +  }],  | 
 | 250 | +});  | 
 | 251 | + | 
 | 252 | +// Attach ASG to Target Group  | 
 | 253 | +new aws.autoscaling.Attachment(`${projectName}-asg-attachment`, {  | 
 | 254 | +  autoscalingGroupName: asg.name,  | 
 | 255 | +  lbTargetGroupArn: targetGroup.arn,  | 
 | 256 | +});  | 
 | 257 | + | 
 | 258 | +// CloudWatch Alarms  | 
 | 259 | +const cpuAlarm = new aws.cloudwatch.MetricAlarm(`${projectName}-cpu-alarm`, {  | 
 | 260 | +  comparisonOperator: "GreaterThanThreshold",  | 
 | 261 | +  evaluationPeriods: 2,  | 
 | 262 | +  metricName: "CPUUtilization",  | 
 | 263 | +  namespace: "AWS/EC2",  | 
 | 264 | +  period: 300,  | 
 | 265 | +  statistic: "Average",  | 
 | 266 | +  threshold: 80,  | 
 | 267 | +  alarmDescription: "This metric monitors ec2 cpu utilization",  | 
 | 268 | +  dimensions: {  | 
 | 269 | +    AutoScalingGroupName: asg.name,  | 
 | 270 | +  },  | 
 | 271 | +});  | 
 | 272 | + | 
 | 273 | +// Export values  | 
 | 274 | +export const vpcId = vpc.vpcId;  | 
 | 275 | +export const albDnsName = alb.dnsName;  | 
 | 276 | +export const asgName = asg.name;  | 
0 commit comments