mirror of
https://github.com/windmill-labs/windmill.git
synced 2025-12-03 13:35:30 +00:00
AWS cloudformation quicklaunch (#5650)
This commit is contained in:
46
examples/deploy/aws-eks-cloudformation/README.md
Normal file
46
examples/deploy/aws-eks-cloudformation/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# windmill-cloudformation
|
||||
Cloudformation Template for Windmill on AWS EKS
|
||||
|
||||
## Overview
|
||||
|
||||
This CloudFormation template automatically deploys Windmill on AWS EKS. The deployment includes:
|
||||
|
||||
- An EKS cluster with configurable node types and sizes
|
||||
- An RDS PostgreSQL database for Windmill data
|
||||
- AWS Load Balancer Controller for handling ingress traffic
|
||||
- Proper network configuration with VPC, subnets, and security groups
|
||||
- A fully automated installation of Windmill via Helm
|
||||
|
||||
## Parameters
|
||||
|
||||
The template accepts various parameters to customize your deployment:
|
||||
|
||||
- **NodeInstanceType**: EC2 instance type for EKS worker nodes (t3.small to r5.2xlarge)
|
||||
- **NodeGroupSize**: Number of EKS worker nodes
|
||||
- **RdsInstanceClass**: RDS instance class for the PostgreSQL database (db.t3.micro to db.r5.2xlarge)
|
||||
- **DBPassword**: Password for the PostgreSQL database
|
||||
- **WorkerReplicas**: Number of Windmill worker replicas
|
||||
- **NativeWorkerReplicas**: Number of Windmill native worker replicas
|
||||
- **Enterprise**: Enable Windmill [Enterprise features](https://www.windmill.dev/docs/misc/plans_details#upgrading-to-enterprise-edition) (requires license key)
|
||||
|
||||
## Customization
|
||||
|
||||
To modify the Helm chart configuration or update the template, refer to the official Windmill Helm chart repository:
|
||||
[https://github.com/windmill-labs/windmill-helm-charts](https://github.com/windmill-labs/windmill-helm-charts)
|
||||
|
||||
## Documentation
|
||||
|
||||
For more information about Windmill's Helm chart deployment options, see:
|
||||
[https://www.windmill.dev/docs/advanced/self_host#helm-chart](https://www.windmill.dev/docs/advanced/self_host#helm-chart)
|
||||
|
||||
For detailed information about setting up RDS for Windmill on AWS:
|
||||
[https://www.windmill.dev/docs/advanced/self_host/aws_ecs#create-a-rds-database](https://www.windmill.dev/docs/advanced/self_host/aws_ecs#create-a-rds-database)
|
||||
|
||||
## Deployment
|
||||
|
||||
1. Upload the CloudFormation template to your AWS account
|
||||
2. Fill in the required parameters
|
||||
3. Deploy the stack
|
||||
4. Access Windmill using the URL provided in the Outputs section of the stack
|
||||
|
||||
After deployment, you can access Windmill via the LoadBalancer URL shown in the CloudFormation stack outputs.
|
||||
688
examples/deploy/aws-eks-cloudformation/quicklaunch.yaml
Normal file
688
examples/deploy/aws-eks-cloudformation/quicklaunch.yaml
Normal file
@@ -0,0 +1,688 @@
|
||||
AWSTemplateFormatVersion: "2010-09-09"
|
||||
Description: Deploy Windmill on EKS with Helm
|
||||
|
||||
Parameters:
|
||||
NodeInstanceType:
|
||||
Type: String
|
||||
Default: t3.medium
|
||||
AllowedValues:
|
||||
- t3.small
|
||||
- t3.medium
|
||||
- t3.large
|
||||
- t3.xlarge
|
||||
- t3.2xlarge
|
||||
- m5.large
|
||||
- m5.xlarge
|
||||
- m5.2xlarge
|
||||
- m5.4xlarge
|
||||
- c5.large
|
||||
- c5.xlarge
|
||||
- c5.2xlarge
|
||||
- r5.large
|
||||
- r5.xlarge
|
||||
- r5.2xlarge
|
||||
Description: EC2 instance type for the EKS worker nodes
|
||||
NodeGroupSize:
|
||||
Type: Number
|
||||
Default: 2
|
||||
RdsInstanceClass:
|
||||
Type: String
|
||||
Default: db.t3.small
|
||||
AllowedValues:
|
||||
- db.t3.micro
|
||||
- db.t3.small
|
||||
- db.t3.medium
|
||||
- db.t3.large
|
||||
- db.t3.xlarge
|
||||
- db.m5.large
|
||||
- db.m5.xlarge
|
||||
- db.m5.2xlarge
|
||||
- db.r5.large
|
||||
- db.r5.xlarge
|
||||
- db.r5.2xlarge
|
||||
Description: RDS instance class for the PostgreSQL database
|
||||
DBPassword:
|
||||
Type: String
|
||||
NoEcho: true
|
||||
WorkerReplicas:
|
||||
Type: Number
|
||||
Default: 2
|
||||
NativeWorkerReplicas:
|
||||
Type: Number
|
||||
Default: 1
|
||||
Enterprise:
|
||||
Type: String
|
||||
Default: false
|
||||
AllowedValues:
|
||||
- true
|
||||
- false
|
||||
Description: Enable Windmill Enterprise features (requires license key)
|
||||
|
||||
Mappings:
|
||||
RegionMap:
|
||||
us-east-1:
|
||||
AMI: ami-0cff7528ff583bf9a
|
||||
us-east-2:
|
||||
AMI: ami-0cd3c7f72edd5b06d
|
||||
us-west-1:
|
||||
AMI: ami-0d9858aa3c6322f73
|
||||
us-west-2:
|
||||
AMI: ami-098e42ae54c764c35
|
||||
ca-central-1:
|
||||
AMI: ami-00f881f027a6d74a0
|
||||
eu-west-1:
|
||||
AMI: ami-04dd4500af104442f
|
||||
eu-west-2:
|
||||
AMI: ami-0eb260c4d5475b901
|
||||
eu-west-3:
|
||||
AMI: ami-05e8e20cef0eaa9d0
|
||||
eu-central-1:
|
||||
AMI: ami-0bad4a5e987bdebde
|
||||
ap-northeast-1:
|
||||
AMI: ami-0b7546e839d7ace12
|
||||
ap-northeast-2:
|
||||
AMI: ami-0fd0765afb77bcca7
|
||||
ap-southeast-1:
|
||||
AMI: ami-0c802847a7dd848c0
|
||||
ap-southeast-2:
|
||||
AMI: ami-07620139298af599e
|
||||
ap-south-1:
|
||||
AMI: ami-0851b76e8b1bce90b
|
||||
sa-east-1:
|
||||
AMI: ami-054a31f1b3bf90920
|
||||
|
||||
Resources:
|
||||
VPC:
|
||||
Type: AWS::EC2::VPC
|
||||
Properties:
|
||||
CidrBlock: 10.0.0.0/16
|
||||
EnableDnsSupport: true
|
||||
EnableDnsHostnames: true
|
||||
Tags:
|
||||
- Key: Name
|
||||
Value: !Sub "${AWS::StackName}-vpc"
|
||||
- Key: !Sub "kubernetes.io/cluster/${AWS::StackName}-cluster"
|
||||
Value: shared
|
||||
|
||||
InternetGateway:
|
||||
Type: AWS::EC2::InternetGateway
|
||||
|
||||
AttachGateway:
|
||||
Type: AWS::EC2::VPCGatewayAttachment
|
||||
Properties:
|
||||
VpcId: !Ref VPC
|
||||
InternetGatewayId: !Ref InternetGateway
|
||||
|
||||
PublicSubnet1:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
VpcId: !Ref VPC
|
||||
CidrBlock: 10.0.1.0/24
|
||||
AvailabilityZone: !Select [0, !GetAZs ""]
|
||||
MapPublicIpOnLaunch: true
|
||||
Tags:
|
||||
- Key: kubernetes.io/role/elb
|
||||
Value: "1"
|
||||
- Key: !Sub "kubernetes.io/cluster/${AWS::StackName}-cluster"
|
||||
Value: shared
|
||||
- Key: Name
|
||||
Value: !Sub ${AWS::StackName}-public-subnet-1
|
||||
|
||||
PublicSubnet2:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
VpcId: !Ref VPC
|
||||
CidrBlock: 10.0.2.0/24
|
||||
AvailabilityZone: !Select [1, !GetAZs ""]
|
||||
MapPublicIpOnLaunch: true
|
||||
Tags:
|
||||
- Key: kubernetes.io/role/elb
|
||||
Value: "1"
|
||||
- Key: !Sub "kubernetes.io/cluster/${AWS::StackName}-cluster"
|
||||
Value: shared
|
||||
- Key: Name
|
||||
Value: !Sub ${AWS::StackName}-public-subnet-2
|
||||
|
||||
RouteTable:
|
||||
Type: AWS::EC2::RouteTable
|
||||
Properties:
|
||||
VpcId: !Ref VPC
|
||||
|
||||
PublicRoute:
|
||||
Type: AWS::EC2::Route
|
||||
DependsOn: AttachGateway
|
||||
Properties:
|
||||
RouteTableId: !Ref RouteTable
|
||||
DestinationCidrBlock: 0.0.0.0/0
|
||||
GatewayId: !Ref InternetGateway
|
||||
|
||||
SubnetRouteTableAssociation1:
|
||||
Type: AWS::EC2::SubnetRouteTableAssociation
|
||||
DependsOn: PublicRoute
|
||||
Properties:
|
||||
SubnetId: !Ref PublicSubnet1
|
||||
RouteTableId: !Ref RouteTable
|
||||
|
||||
SubnetRouteTableAssociation2:
|
||||
Type: AWS::EC2::SubnetRouteTableAssociation
|
||||
DependsOn: PublicRoute
|
||||
Properties:
|
||||
SubnetId: !Ref PublicSubnet2
|
||||
RouteTableId: !Ref RouteTable
|
||||
|
||||
EKSClusterRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service:
|
||||
- eks.amazonaws.com
|
||||
- ec2.amazonaws.com
|
||||
Action:
|
||||
- sts:AssumeRole
|
||||
ManagedPolicyArns:
|
||||
- arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
|
||||
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
|
||||
Policies:
|
||||
- PolicyName: EKSAccess
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- eks:*
|
||||
- ec2:DescribeInstances
|
||||
- ec2:DescribeRouteTables
|
||||
- ec2:DescribeSecurityGroups
|
||||
- ec2:DescribeSubnets
|
||||
- ec2:DescribeVpcs
|
||||
- iam:GetRole
|
||||
- iam:ListRoles
|
||||
Resource: "*"
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ssm:GetParameter
|
||||
- ssm:PutParameter
|
||||
Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${AWS::StackName}/*"
|
||||
- PolicyName: KubernetesAccess
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- eks:DescribeCluster
|
||||
- eks:ListClusters
|
||||
- eks:AccessKubernetesApi
|
||||
Resource: !Sub "arn:aws:eks:${AWS::Region}:${AWS::AccountId}:cluster/${AWS::StackName}-cluster"
|
||||
|
||||
EKSNodeRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service:
|
||||
- ec2.amazonaws.com
|
||||
Action:
|
||||
- sts:AssumeRole
|
||||
ManagedPolicyArns:
|
||||
- arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
|
||||
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
|
||||
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
|
||||
|
||||
EKSCluster:
|
||||
Type: AWS::EKS::Cluster
|
||||
Properties:
|
||||
Name: !Sub "${AWS::StackName}-cluster"
|
||||
RoleArn: !GetAtt EKSClusterRole.Arn
|
||||
ResourcesVpcConfig:
|
||||
SubnetIds:
|
||||
- !Ref PublicSubnet1
|
||||
- !Ref PublicSubnet2
|
||||
EndpointPublicAccess: true
|
||||
AccessConfig:
|
||||
AuthenticationMode: API_AND_CONFIG_MAP
|
||||
BootstrapClusterCreatorAdminPermissions: true
|
||||
DependsOn:
|
||||
- PublicRoute
|
||||
- VPCCleanup
|
||||
|
||||
EKSClusterAccess:
|
||||
Type: AWS::EKS::AccessEntry
|
||||
Properties:
|
||||
ClusterName: !Ref EKSCluster
|
||||
PrincipalArn: !GetAtt EKSClusterRole.Arn
|
||||
Type: STANDARD
|
||||
Username: admin
|
||||
AccessPolicies:
|
||||
- PolicyArn: arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy
|
||||
AccessScope:
|
||||
Type: cluster
|
||||
|
||||
EKSNodeGroup:
|
||||
Type: AWS::EKS::Nodegroup
|
||||
DependsOn:
|
||||
- WindmillDB
|
||||
- EKSCluster
|
||||
Properties:
|
||||
ClusterName: !Ref EKSCluster
|
||||
NodeRole: !GetAtt EKSNodeRole.Arn
|
||||
Subnets:
|
||||
- !Ref PublicSubnet1
|
||||
- !Ref PublicSubnet2
|
||||
ScalingConfig:
|
||||
MinSize: 1
|
||||
DesiredSize: !Ref NodeGroupSize
|
||||
MaxSize: 4
|
||||
InstanceTypes:
|
||||
- !Ref NodeInstanceType
|
||||
|
||||
WindmillDBSubnetGroup:
|
||||
Type: AWS::RDS::DBSubnetGroup
|
||||
Properties:
|
||||
DBSubnetGroupDescription: Subnet group for RDS instance
|
||||
SubnetIds:
|
||||
- !Ref PublicSubnet1
|
||||
- !Ref PublicSubnet2
|
||||
|
||||
WindmillDBSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: Allow PostgreSQL access from EKS nodes
|
||||
VpcId: !Ref VPC
|
||||
SecurityGroupIngress:
|
||||
- IpProtocol: tcp
|
||||
FromPort: 5432
|
||||
ToPort: 5432
|
||||
CidrIp: 10.0.0.0/16
|
||||
|
||||
WindmillDB:
|
||||
Type: AWS::RDS::DBInstance
|
||||
Properties:
|
||||
DBInstanceIdentifier: !Sub "${AWS::StackName}-db"
|
||||
AllocatedStorage: 20
|
||||
DBInstanceClass: !Ref RdsInstanceClass
|
||||
Engine: postgres
|
||||
EngineVersion: 17.2
|
||||
MasterUsername: postgres
|
||||
MasterUserPassword: !Ref DBPassword
|
||||
DBName: windmill
|
||||
PubliclyAccessible: false
|
||||
DBSubnetGroupName: !Ref WindmillDBSubnetGroup
|
||||
VPCSecurityGroups:
|
||||
- !Ref WindmillDBSecurityGroup
|
||||
DependsOn:
|
||||
- WindmillDBSubnetGroup
|
||||
|
||||
WindmillInstallerInstanceProfile:
|
||||
Type: AWS::IAM::InstanceProfile
|
||||
Properties:
|
||||
Roles:
|
||||
- !Ref EKSClusterRole
|
||||
|
||||
WindmillInstallerSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: Security group for Windmill installer instance
|
||||
VpcId: !Ref VPC
|
||||
SecurityGroupEgress:
|
||||
- IpProtocol: -1
|
||||
FromPort: -1
|
||||
ToPort: -1
|
||||
CidrIp: 0.0.0.0/0
|
||||
|
||||
WindmillInstaller:
|
||||
Type: AWS::EC2::Instance
|
||||
CreationPolicy:
|
||||
ResourceSignal:
|
||||
Timeout: PT30M # Gives 30 minutes for the installation to complete
|
||||
DependsOn:
|
||||
- EKSNodeGroup
|
||||
- WindmillDB
|
||||
Properties:
|
||||
ImageId: !FindInMap [RegionMap, !Ref "AWS::Region", AMI]
|
||||
InstanceType: t3.micro
|
||||
IamInstanceProfile: !Ref WindmillInstallerInstanceProfile
|
||||
SubnetId: !Ref PublicSubnet1
|
||||
SecurityGroupIds:
|
||||
- !Ref WindmillInstallerSecurityGroup
|
||||
UserData:
|
||||
Fn::Base64: !Sub |
|
||||
#!/bin/bash
|
||||
set -e # Exit on any error
|
||||
|
||||
# Install required tools
|
||||
yum update -y
|
||||
yum install -y aws-cli jq postgresql15 aws-cfn-bootstrap
|
||||
|
||||
# Set up logging directory with correct permissions
|
||||
mkdir -p /var/log/windmill-installer
|
||||
touch /var/log/windmill-installer/install.log
|
||||
|
||||
# Create installation directory
|
||||
mkdir -p /opt/windmill-installer
|
||||
|
||||
# Install kubectl
|
||||
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
|
||||
chmod +x kubectl
|
||||
mv kubectl /usr/local/bin/
|
||||
|
||||
# Install helm
|
||||
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
|
||||
chmod +x get_helm.sh
|
||||
./get_helm.sh
|
||||
|
||||
# Create the installation script
|
||||
cat << 'EOF' > /opt/windmill-installer/install.sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Configure kubectl
|
||||
aws sts get-caller-identity > /dev/null
|
||||
export AWS_SDK_LOAD_CONFIG=1
|
||||
export KUBECONFIG=/root/.kube/config
|
||||
aws eks update-kubeconfig --name ${AWS::StackName}-cluster --region ${AWS::Region}
|
||||
|
||||
# Add debugging for each kubectl attempt
|
||||
echo "HOME: $HOME"
|
||||
echo "KUBECONFIG: $KUBECONFIG"
|
||||
echo "User: $(whoami)"
|
||||
echo "AWS Identity: $(aws sts get-caller-identity)"
|
||||
echo "Trying kubectl command..."
|
||||
kubectl get nodes || echo "Command failed with status $?"
|
||||
|
||||
echo "Waiting for EKS nodes to be ready..."
|
||||
while true; do
|
||||
# Force credential refresh on each attempt
|
||||
aws sts get-caller-identity > /dev/null
|
||||
aws eks update-kubeconfig --name ${AWS::StackName}-cluster --region ${AWS::Region}
|
||||
|
||||
if kubectl get nodes &>/dev/null; then
|
||||
READY_NODES=$(kubectl get nodes -o json | jq -r '.items[] | select(.status.conditions[] | select(.type=="Ready" and .status=="True")) | .metadata.name' | wc -l)
|
||||
DESIRED_NODES=${NodeGroupSize}
|
||||
if [ "$READY_NODES" -eq "$DESIRED_NODES" ]; then
|
||||
echo "All nodes are ready"
|
||||
break
|
||||
fi
|
||||
echo "Found $READY_NODES ready nodes out of $DESIRED_NODES desired nodes"
|
||||
else
|
||||
echo "Waiting for cluster access..."
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
|
||||
echo "Installing AWS Load Balancer Controller..."
|
||||
# Install AWS Load Balancer Controller
|
||||
helm repo add eks https://aws.github.io/eks-charts
|
||||
helm repo update
|
||||
|
||||
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
|
||||
-n kube-system \
|
||||
--set clusterName=${AWS::StackName}-cluster \
|
||||
--set region=${AWS::Region} \
|
||||
--set vpcId=${VPC}
|
||||
|
||||
echo "Waiting for AWS Load Balancer Controller to be ready..."
|
||||
kubectl wait --namespace kube-system \
|
||||
--for=condition=ready pod \
|
||||
--selector=app.kubernetes.io/name=aws-load-balancer-controller \
|
||||
--timeout=300s
|
||||
|
||||
echo "Waiting for RDS to be available..."
|
||||
while true; do
|
||||
if pg_isready -h ${WindmillDB.Endpoint.Address} -p 5432 -U postgres 2>/dev/null; then
|
||||
echo "Database is ready"
|
||||
break
|
||||
fi
|
||||
echo "Database not ready yet..."
|
||||
sleep 30
|
||||
done
|
||||
|
||||
echo "Creating namespace and installing Windmill..."
|
||||
kubectl create namespace windmill
|
||||
|
||||
# Add helm repo and install Windmill
|
||||
helm repo add windmill https://windmill-labs.github.io/windmill-helm-charts
|
||||
helm repo update
|
||||
|
||||
helm install ${AWS::StackName} windmill/windmill \
|
||||
--namespace windmill \
|
||||
--set windmill.databaseUrl="postgres://postgres:${DBPassword}@${WindmillDB.Endpoint.Address}/windmill?sslmode=require" \
|
||||
--set windmill.baseDomain=windmill.local \
|
||||
--set windmill.baseProtocol=http \
|
||||
--set windmill.appReplicas=${WorkerReplicas} \
|
||||
--set windmill.lspReplicas=2 \
|
||||
--set windmill.workerGroups[0].name=default \
|
||||
--set windmill.workerGroups[0].mode=worker \
|
||||
--set windmill.workerGroups[0].replicas=${WorkerReplicas} \
|
||||
--set windmill.workerGroups[1].name=native \
|
||||
--set windmill.workerGroups[1].mode=worker \
|
||||
--set windmill.workerGroups[1].replicas=${NativeWorkerReplicas} \
|
||||
--set windmill.app.service.spec.type=LoadBalancer \
|
||||
--set windmill.app.service.spec.sessionAffinity=None \
|
||||
--set windmill.app.service.port=8000 \
|
||||
--set windmill.app.service.ports[0].port=8000 \
|
||||
--set windmill.app.service.ports[0].targetPort=8000 \
|
||||
--set windmill.app.service.ports[0].protocol=TCP \
|
||||
--set postgresql.enabled=false \
|
||||
--set enterprise.enabled=${Enterprise}
|
||||
|
||||
# Change service to LoadBalancer
|
||||
echo "Changing service to LoadBalancer"
|
||||
kubectl patch service windmill-app -n windmill -p '{"spec":{"type":"LoadBalancer","sessionAffinity":"None"}}'
|
||||
kubectl patch service windmill-app -n windmill -p '{"spec":{"ports":[{"name":"api","port":8000,"targetPort":8000,"protocol":"TCP"},{"name":"http","port":80,"targetPort":8000,"protocol":"TCP"}]}}'
|
||||
|
||||
# Wait for LoadBalancer to get an address
|
||||
echo "Waiting for LoadBalancer address..."
|
||||
while true; do
|
||||
LB_HOSTNAME=$(kubectl get svc -n windmill windmill-app -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null)
|
||||
if [ ! -z "$LB_HOSTNAME" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Waiting for LoadBalancer hostname..."
|
||||
sleep 30
|
||||
done
|
||||
|
||||
# Store the LoadBalancer hostname in SSM
|
||||
aws ssm put-parameter \
|
||||
--region ${AWS::Region} \
|
||||
--name "/${AWS::StackName}/loadbalancer-hostname" \
|
||||
--value "$LB_HOSTNAME" \
|
||||
--type "String" \
|
||||
--overwrite
|
||||
|
||||
# Signal CloudFormation that installation is complete
|
||||
echo "Signal CloudFormation that installation is complete"
|
||||
/opt/aws/bin/cfn-signal -e $? \
|
||||
--stack ${AWS::StackName} \
|
||||
--resource WindmillInstaller \
|
||||
--region ${AWS::Region}
|
||||
|
||||
# Self-terminate this instance
|
||||
echo "Self-terminating instance"
|
||||
aws ec2 terminate-instances --instance-ids $(curl -s http://169.254.169.254/latest/meta-data/instance-id) --region ${AWS::Region}
|
||||
EOF
|
||||
|
||||
# Set permissions and run the installation script
|
||||
chmod +x /opt/windmill-installer/install.sh
|
||||
|
||||
# Run the installation script directly (not as ec2-user)
|
||||
cd /opt/windmill-installer && ./install.sh > /var/log/windmill-installer/install.log 2>&1
|
||||
|
||||
LoadBalancerHostnameLookup:
|
||||
Type: Custom::SSMParameterLookup
|
||||
DependsOn: WindmillInstaller
|
||||
Properties:
|
||||
ServiceToken: !GetAtt LookupSSMParameterFunction.Arn
|
||||
ParameterName: !Sub "/${AWS::StackName}/loadbalancer-hostname"
|
||||
|
||||
LookupSSMParameterFunction:
|
||||
Type: AWS::Lambda::Function
|
||||
Properties:
|
||||
Handler: index.handler
|
||||
Role: !GetAtt LambdaExecutionRole.Arn
|
||||
Timeout: 300
|
||||
Runtime: nodejs18.x
|
||||
Code:
|
||||
ZipFile: !Sub |
|
||||
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm');
|
||||
const response = require('cfn-response');
|
||||
|
||||
exports.handler = async (event, context) => {
|
||||
if (event.RequestType === 'Delete') {
|
||||
return response.send(event, context, response.SUCCESS);
|
||||
}
|
||||
|
||||
try {
|
||||
const ssmClient = new SSMClient();
|
||||
|
||||
// Loop to check for the parameter until it's not "pending"
|
||||
let tries = 0;
|
||||
let paramValue = "pending";
|
||||
|
||||
while (paramValue === "pending" && tries < 20) {
|
||||
const params = {
|
||||
Name: event.ResourceProperties.ParameterName,
|
||||
WithDecryption: false
|
||||
};
|
||||
|
||||
const result = await ssmClient.send(new GetParameterCommand(params));
|
||||
paramValue = result.Parameter.Value;
|
||||
|
||||
if (paramValue === "pending") {
|
||||
await new Promise(resolve => setTimeout(resolve, 15000)); // wait 15 seconds
|
||||
tries++;
|
||||
}
|
||||
}
|
||||
|
||||
return response.send(event, context, response.SUCCESS, {
|
||||
HostnameValue: paramValue
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return response.send(event, context, response.FAILED, { error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
LambdaExecutionRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: lambda.amazonaws.com
|
||||
Action: sts:AssumeRole
|
||||
ManagedPolicyArns:
|
||||
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
||||
Policies:
|
||||
- PolicyName: SSMParameterAccess
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ssm:GetParameter
|
||||
Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${AWS::StackName}/*"
|
||||
|
||||
VPCCleanupFunction:
|
||||
Type: AWS::Lambda::Function
|
||||
Properties:
|
||||
Handler: index.handler
|
||||
Role: !GetAtt VPCCleanupRole.Arn
|
||||
Timeout: 300
|
||||
Runtime: nodejs18.x
|
||||
Code:
|
||||
ZipFile: |
|
||||
const { ElasticLoadBalancingClient, DescribeLoadBalancersCommand,
|
||||
DeleteLoadBalancerCommand } = require('@aws-sdk/client-elastic-load-balancing');
|
||||
const response = require('cfn-response');
|
||||
|
||||
exports.handler = async (event, context) => {
|
||||
if (event.RequestType !== 'Delete') {
|
||||
return response.send(event, context, response.SUCCESS);
|
||||
}
|
||||
|
||||
try {
|
||||
const elb = new ElasticLoadBalancingClient();
|
||||
const vpcId = event.ResourceProperties.VpcId;
|
||||
|
||||
// Find and delete Classic Load Balancers in the VPC
|
||||
const lbResponse = await elb.send(new DescribeLoadBalancersCommand({}));
|
||||
let deleted = false;
|
||||
|
||||
for (const lb of lbResponse.LoadBalancerDescriptions || []) {
|
||||
if (lb.VPCId === vpcId) {
|
||||
console.log(`Deleting Classic Load Balancer: ${lb.LoadBalancerName}`);
|
||||
await elb.send(new DeleteLoadBalancerCommand({
|
||||
LoadBalancerName: lb.LoadBalancerName
|
||||
}));
|
||||
deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
// Wait for deletion to complete
|
||||
console.log('Waiting 30 seconds for load balancer deletion to complete...');
|
||||
await new Promise(r => setTimeout(r, 30000));
|
||||
}
|
||||
|
||||
return response.send(event, context, response.SUCCESS);
|
||||
} catch (error) {
|
||||
console.error('Error deleting load balancers:', error);
|
||||
return response.send(event, context, response.FAILED, {error: error.message});
|
||||
}
|
||||
};
|
||||
|
||||
VPCCleanupRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: lambda.amazonaws.com
|
||||
Action: sts:AssumeRole
|
||||
ManagedPolicyArns:
|
||||
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
||||
Policies:
|
||||
- PolicyName: VPCCleanupPolicy
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
- ec2:DescribeAddresses
|
||||
- ec2:DisassociateAddress
|
||||
- ec2:DescribeNetworkInterfaces
|
||||
- elasticloadbalancing:DescribeLoadBalancers
|
||||
- elasticloadbalancing:DeleteLoadBalancer
|
||||
- elasticloadbalancingv2:DescribeLoadBalancers
|
||||
- elasticloadbalancingv2:DeleteLoadBalancer
|
||||
Resource: "*"
|
||||
|
||||
VPCCleanup:
|
||||
Type: Custom::VPCCleanup
|
||||
Properties:
|
||||
ServiceToken: !GetAtt VPCCleanupFunction.Arn
|
||||
VpcId: !Ref VPC
|
||||
|
||||
Outputs:
|
||||
ClusterName:
|
||||
Description: EKS cluster name
|
||||
Value: !Sub "${AWS::StackName}-cluster"
|
||||
|
||||
DatabaseEndpoint:
|
||||
Description: RDS instance endpoint
|
||||
Value: !GetAtt WindmillDB.Endpoint.Address
|
||||
|
||||
LoadBalancerHostname:
|
||||
Description: Windmill LoadBalancer hostname
|
||||
Value: !Sub "http://${LoadBalancerHostnameLookup.HostnameValue}"
|
||||
Reference in New Issue
Block a user