Skip to content

Instantly share code, notes, and snippets.

@nrashok
Last active September 17, 2025 06:30
Show Gist options
  • Select an option

  • Save nrashok/0338beb5b644ca5b440f2ac4f62b2e4d to your computer and use it in GitHub Desktop.

Select an option

Save nrashok/0338beb5b644ca5b440f2ac4f62b2e4d to your computer and use it in GitHub Desktop.

Revisions

  1. nrashok revised this gist Sep 17, 2025. 1 changed file with 33 additions and 47 deletions.
    80 changes: 33 additions & 47 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -1,19 +1,16 @@
    AWSTemplateFormatVersion: '2010-09-09'
    Description: Automate stopping/starting EC2, RDS and managed EKS NodeGroups with Step Functions + EventBridge + SSM
    Description: Automate stopping/starting EC2 and managed EKS NodeGroups with Step Functions + EventBridge + SSM

    Parameters:
    InstanceIds:
    Type: CommaDelimitedList
    Default: i-07c33ff5a82b668xx,i-047a90a37986a66xx
    Default: i-07c33ff5a82b668cc,i-047a90a37986a669e
    ClusterName:
    Type: String
    Default: ashok-eks-clusterdev
    Default: mr-blues-dev
    NodeGroups:
    Type: CommaDelimitedList
    Default: t3-spot-private-ng
    RDSInstanceIds:
    Type: CommaDelimitedList
    Default: mydbinstance1
    Default: t3-spot-private
    Region:
    Type: String
    Default: ap-south-1
    @@ -43,7 +40,6 @@ Resources:
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AmazonRDSFullAccess
    Policies:
    - PolicyName: EKSCustomNodegroupScaling
    PolicyDocument:
    @@ -75,59 +71,57 @@ Resources:
    def get_ng_name(ng):
    if ng.startswith("arn:aws:eks:"):
    return ng.split("/")[2]
    # ARN format: arn:aws:eks:region:account:nodegroup/<cluster>/<name>/<uuid>
    return ng.split("/")[2] # extract <name>
    return ng
    def lambda_handler(event, context):
    action = event.get("Action")
    cluster_name = event.get("ClusterName", os.environ.get("CLUSTER"))
    nodegroups = event.get("NodeGroups", [])
    instances = event.get("InstanceIds", [])
    rds_instances = event.get("RDSInstanceIds", [])
    start_size = int(event.get("StartSize", os.environ.get("START_SIZE", "2")))
    min_size = int(event.get("MinSize", os.environ.get("MIN_SIZE", "1")))
    region = event.get("Region", os.environ.get("REGION", "ap-south-1"))
    ec2 = boto3.client("ec2", region_name=region)
    eks = boto3.client("eks", region_name=region)
    rds = boto3.client("rds", region_name=region)
    # Stop/start EC2
    # Stop/start EC2 instances
    if instances:
    if action == "stop":
    ec2.stop_instances(InstanceIds=instances)
    elif action == "start":
    ec2.start_instances(InstanceIds=instances)
    # Scale Nodegroups
    # Scale managed nodegroups
    for ng in nodegroups:
    ng_name = get_ng_name(ng)
    if action == "stop":
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={"minSize": 0, "desiredSize": 0}
    scalingConfig={
    "minSize": 0,
    "desiredSize": 0
    }
    )
    elif action == "start":
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={"minSize": min_size, "desiredSize": start_size}
    scalingConfig={
    "minSize": min_size,
    "desiredSize": start_size
    }
    )
    # Stop/start RDS
    for rds_id in rds_instances:
    if action == "stop":
    rds.stop_db_instance(DBInstanceIdentifier=rds_id)
    elif action == "start":
    rds.start_db_instance(DBInstanceIdentifier=rds_id)
    return {
    "status": "success",
    "action": action,
    "instances": instances,
    "nodegroups": nodegroups,
    "rds_instances": rds_instances
    "nodegroups": nodegroups
    }
    StepFunctionRole:
    @@ -156,7 +150,7 @@ Resources:
    DefinitionString:
    Fn::Sub: |
    {
    "Comment": "Stop/Start EC2, RDS and managed EKS NodeGroups",
    "Comment": "Stop/Start EC2 and managed EKS NodeGroups",
    "StartAt": "StopStartAction",
    "States": {
    "StopStartAction": {
    @@ -184,10 +178,9 @@ Resources:
    Input: |
    {
    "Action": "stop",
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "RDSInstanceIds": ["mydbinstance1"],
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "Region": "ap-south-1"
    }
    @@ -203,10 +196,9 @@ Resources:
    Input: |
    {
    "Action": "start",
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "RDSInstanceIds": ["mydbinstance1"],
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "Region": "ap-south-1",
    "StartSize": 2,
    "MinSize": 1
    @@ -218,28 +210,24 @@ Resources:
    DocumentType: Automation
    Content:
    schemaVersion: '0.3'
    description: "Manually trigger stop/start for EC2, RDS and managed EKS NodeGroups"
    description: "Manually trigger stop/start for EC2 + managed EKS NodeGroups"
    parameters:
    Action:
    type: String
    description: "Action to perform (stop/start)"
    allowedValues: ["stop", "start"]
    InstanceIds:
    type: StringList
    description: "List of EC2 instance IDs"
    default: ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"]
    description: "List of EC2 instance IDs to start/stop"
    default: ["i-07c33ff5a82b668cc","i-047a90a37986a669e"]
    NodeGroups:
    type: StringList
    description: "EKS NodeGroups"
    default: ["t3-spot-private-ng"]
    RDSInstanceIds:
    type: StringList
    description: "List of RDS DB Instance IDs"
    default: ["mydbinstance1"]
    description: "Managed NodeGroup names or ARNs"
    default: ["t3-spot-private"]
    ClusterName:
    type: String
    description: "EKS Cluster name"
    default: "ashok-eks-clusterdev"
    default: "mr-blues-dev"
    StepFunctionArn:
    type: String
    description: "Step Function ARN"
    @@ -260,7 +248,6 @@ Resources:
    "ClusterName": event["ClusterName"],
    "InstanceIds": event["InstanceIds"],
    "NodeGroups": event["NodeGroups"],
    "RDSInstanceIds": event["RDSInstanceIds"],
    "MinSize": event.get("MinSize", 1),
    "StartSize": event.get("StartSize", 2)
    })
    @@ -271,16 +258,15 @@ Resources:
    ClusterName: "{{ClusterName}}"
    InstanceIds: "{{InstanceIds}}"
    NodeGroups: "{{NodeGroups}}"
    RDSInstanceIds: "{{RDSInstanceIds}}"
    StepFunctionArn: !Ref AutomationStateMachine

    Outputs:
    StepFunctionArn:
    Description: ARN of the Step Function
    Value: !Ref AutomationStateMachine
    LambdaName:
    Description: Lambda function handling EC2/RDS/EKS actions
    Description: Lambda function handling EC2/managed EKS actions
    Value: !Ref StopStartLambda
    ManualSSMDocumentName:
    Description: Name of the SSM document for manual trigger
    Value: !Ref ManualTriggerSSMDocument
    Value: !Ref ManualTriggerSSMDocument
  2. nrashok revised this gist Sep 17, 2025. 1 changed file with 47 additions and 33 deletions.
    80 changes: 47 additions & 33 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -1,16 +1,19 @@
    AWSTemplateFormatVersion: '2010-09-09'
    Description: Automate stopping/starting EC2 and managed EKS NodeGroups with Step Functions + EventBridge + SSM
    Description: Automate stopping/starting EC2, RDS and managed EKS NodeGroups with Step Functions + EventBridge + SSM

    Parameters:
    InstanceIds:
    Type: CommaDelimitedList
    Default: i-07c33ff5a82b668cc,i-047a90a37986a669e
    Default: i-07c33ff5a82b668xx,i-047a90a37986a66xx
    ClusterName:
    Type: String
    Default: mr-blues-dev
    Default: ashok-eks-clusterdev
    NodeGroups:
    Type: CommaDelimitedList
    Default: t3-spot-private
    Default: t3-spot-private-ng
    RDSInstanceIds:
    Type: CommaDelimitedList
    Default: mydbinstance1
    Region:
    Type: String
    Default: ap-south-1
    @@ -40,6 +43,7 @@ Resources:
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AmazonRDSFullAccess
    Policies:
    - PolicyName: EKSCustomNodegroupScaling
    PolicyDocument:
    @@ -71,57 +75,59 @@ Resources:
    def get_ng_name(ng):
    if ng.startswith("arn:aws:eks:"):
    # ARN format: arn:aws:eks:region:account:nodegroup/<cluster>/<name>/<uuid>
    return ng.split("/")[2] # extract <name>
    return ng.split("/")[2]
    return ng
    def lambda_handler(event, context):
    action = event.get("Action")
    cluster_name = event.get("ClusterName", os.environ.get("CLUSTER"))
    nodegroups = event.get("NodeGroups", [])
    instances = event.get("InstanceIds", [])
    rds_instances = event.get("RDSInstanceIds", [])
    start_size = int(event.get("StartSize", os.environ.get("START_SIZE", "2")))
    min_size = int(event.get("MinSize", os.environ.get("MIN_SIZE", "1")))
    region = event.get("Region", os.environ.get("REGION", "ap-south-1"))
    ec2 = boto3.client("ec2", region_name=region)
    eks = boto3.client("eks", region_name=region)
    rds = boto3.client("rds", region_name=region)
    # Stop/start EC2 instances
    # Stop/start EC2
    if instances:
    if action == "stop":
    ec2.stop_instances(InstanceIds=instances)
    elif action == "start":
    ec2.start_instances(InstanceIds=instances)
    # Scale managed nodegroups
    # Scale Nodegroups
    for ng in nodegroups:
    ng_name = get_ng_name(ng)
    if action == "stop":
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={
    "minSize": 0,
    "desiredSize": 0
    }
    scalingConfig={"minSize": 0, "desiredSize": 0}
    )
    elif action == "start":
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={
    "minSize": min_size,
    "desiredSize": start_size
    }
    scalingConfig={"minSize": min_size, "desiredSize": start_size}
    )
    # Stop/start RDS
    for rds_id in rds_instances:
    if action == "stop":
    rds.stop_db_instance(DBInstanceIdentifier=rds_id)
    elif action == "start":
    rds.start_db_instance(DBInstanceIdentifier=rds_id)
    return {
    "status": "success",
    "action": action,
    "instances": instances,
    "nodegroups": nodegroups
    "nodegroups": nodegroups,
    "rds_instances": rds_instances
    }
    StepFunctionRole:
    @@ -150,7 +156,7 @@ Resources:
    DefinitionString:
    Fn::Sub: |
    {
    "Comment": "Stop/Start EC2 and managed EKS NodeGroups",
    "Comment": "Stop/Start EC2, RDS and managed EKS NodeGroups",
    "StartAt": "StopStartAction",
    "States": {
    "StopStartAction": {
    @@ -178,9 +184,10 @@ Resources:
    Input: |
    {
    "Action": "stop",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "RDSInstanceIds": ["mydbinstance1"],
    "Region": "ap-south-1"
    }
    @@ -196,9 +203,10 @@ Resources:
    Input: |
    {
    "Action": "start",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "RDSInstanceIds": ["mydbinstance1"],
    "Region": "ap-south-1",
    "StartSize": 2,
    "MinSize": 1
    @@ -210,24 +218,28 @@ Resources:
    DocumentType: Automation
    Content:
    schemaVersion: '0.3'
    description: "Manually trigger stop/start for EC2 + managed EKS NodeGroups"
    description: "Manually trigger stop/start for EC2, RDS and managed EKS NodeGroups"
    parameters:
    Action:
    type: String
    description: "Action to perform (stop/start)"
    allowedValues: ["stop", "start"]
    InstanceIds:
    type: StringList
    description: "List of EC2 instance IDs to start/stop"
    default: ["i-07c33ff5a82b668cc","i-047a90a37986a669e"]
    description: "List of EC2 instance IDs"
    default: ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"]
    NodeGroups:
    type: StringList
    description: "Managed NodeGroup names or ARNs"
    default: ["t3-spot-private"]
    description: "EKS NodeGroups"
    default: ["t3-spot-private-ng"]
    RDSInstanceIds:
    type: StringList
    description: "List of RDS DB Instance IDs"
    default: ["mydbinstance1"]
    ClusterName:
    type: String
    description: "EKS Cluster name"
    default: "mr-blues-dev"
    default: "ashok-eks-clusterdev"
    StepFunctionArn:
    type: String
    description: "Step Function ARN"
    @@ -248,6 +260,7 @@ Resources:
    "ClusterName": event["ClusterName"],
    "InstanceIds": event["InstanceIds"],
    "NodeGroups": event["NodeGroups"],
    "RDSInstanceIds": event["RDSInstanceIds"],
    "MinSize": event.get("MinSize", 1),
    "StartSize": event.get("StartSize", 2)
    })
    @@ -258,15 +271,16 @@ Resources:
    ClusterName: "{{ClusterName}}"
    InstanceIds: "{{InstanceIds}}"
    NodeGroups: "{{NodeGroups}}"
    RDSInstanceIds: "{{RDSInstanceIds}}"
    StepFunctionArn: !Ref AutomationStateMachine

    Outputs:
    StepFunctionArn:
    Description: ARN of the Step Function
    Value: !Ref AutomationStateMachine
    LambdaName:
    Description: Lambda function handling EC2/managed EKS actions
    Description: Lambda function handling EC2/RDS/EKS actions
    Value: !Ref StopStartLambda
    ManualSSMDocumentName:
    Description: Name of the SSM document for manual trigger
    Value: !Ref ManualTriggerSSMDocument
    Value: !Ref ManualTriggerSSMDocument
  3. nrashok revised this gist Sep 17, 2025. 1 changed file with 12 additions and 12 deletions.
    24 changes: 12 additions & 12 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -4,13 +4,13 @@ Description: Automate stopping/starting EC2 and managed EKS NodeGroups with Step
    Parameters:
    InstanceIds:
    Type: CommaDelimitedList
    Default: i-07c33ff5a82b668xx,i-047a90a37986a66xx
    Default: i-07c33ff5a82b668cc,i-047a90a37986a669e
    ClusterName:
    Type: String
    Default: ashok-eks-clusterdev
    Default: mr-blues-dev
    NodeGroups:
    Type: CommaDelimitedList
    Default: t3-spot-private-ng
    Default: t3-spot-private
    Region:
    Type: String
    Default: ap-south-1
    @@ -178,9 +178,9 @@ Resources:
    Input: |
    {
    "Action": "stop",
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "Region": "ap-south-1"
    }
    @@ -196,9 +196,9 @@ Resources:
    Input: |
    {
    "Action": "start",
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "Region": "ap-south-1",
    "StartSize": 2,
    "MinSize": 1
    @@ -219,15 +219,15 @@ Resources:
    InstanceIds:
    type: StringList
    description: "List of EC2 instance IDs to start/stop"
    default: ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"]
    default: ["i-07c33ff5a82b668cc","i-047a90a37986a669e"]
    NodeGroups:
    type: StringList
    description: "Managed NodeGroup names or ARNs"
    default: ["t3-spot-private-ng"]
    default: ["t3-spot-private"]
    ClusterName:
    type: String
    description: "EKS Cluster name"
    default: "ashok-eks-clusterdev"
    default: "mr-blues-dev"
    StepFunctionArn:
    type: String
    description: "Step Function ARN"
  4. nrashok revised this gist Sep 12, 2025. 1 changed file with 12 additions and 12 deletions.
    24 changes: 12 additions & 12 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -4,13 +4,13 @@ Description: Automate stopping/starting EC2 and managed EKS NodeGroups with Step
    Parameters:
    InstanceIds:
    Type: CommaDelimitedList
    Default: i-07c33ff5a82b668cc,i-047a90a37986a669e
    Default: i-07c33ff5a82b668xx,i-047a90a37986a66xx
    ClusterName:
    Type: String
    Default: mr-blues-dev
    Default: ashok-eks-clusterdev
    NodeGroups:
    Type: CommaDelimitedList
    Default: t3-spot-private
    Default: t3-spot-private-ng
    Region:
    Type: String
    Default: ap-south-1
    @@ -178,9 +178,9 @@ Resources:
    Input: |
    {
    "Action": "stop",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "Region": "ap-south-1"
    }
    @@ -196,9 +196,9 @@ Resources:
    Input: |
    {
    "Action": "start",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "ClusterName": "ashok-eks-clusterdev",
    "InstanceIds": ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"],
    "NodeGroups": ["t3-spot-private-ng"],
    "Region": "ap-south-1",
    "StartSize": 2,
    "MinSize": 1
    @@ -219,15 +219,15 @@ Resources:
    InstanceIds:
    type: StringList
    description: "List of EC2 instance IDs to start/stop"
    default: ["i-07c33ff5a82b668cc","i-047a90a37986a669e"]
    default: ["i-07c33ff5a82b668xx","i-047a90a37986a66xx"]
    NodeGroups:
    type: StringList
    description: "Managed NodeGroup names or ARNs"
    default: ["t3-spot-private"]
    default: ["t3-spot-private-ng"]
    ClusterName:
    type: String
    description: "EKS Cluster name"
    default: "mr-blues-dev"
    default: "ashok-eks-clusterdev"
    StepFunctionArn:
    type: String
    description: "Step Function ARN"
  5. nrashok revised this gist Sep 12, 2025. 1 changed file with 8 additions and 2 deletions.
    10 changes: 8 additions & 2 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -39,7 +39,7 @@ Resources:
    - arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
    - arn:aws:iam::aws:policy/AmazonEKSNodegroupPolicy
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    Policies:
    - PolicyName: EKSCustomNodegroupScaling
    PolicyDocument:
    @@ -69,6 +69,12 @@ Resources:
    ZipFile: |
    import boto3, os
    def get_ng_name(ng):
    if ng.startswith("arn:aws:eks:"):
    # ARN format: arn:aws:eks:region:account:nodegroup/<cluster>/<name>/<uuid>
    return ng.split("/")[2] # extract <name>
    return ng
    def lambda_handler(event, context):
    action = event.get("Action")
    cluster_name = event.get("ClusterName", os.environ.get("CLUSTER"))
    @@ -90,7 +96,7 @@ Resources:
    # Scale managed nodegroups
    for ng in nodegroups:
    ng_name = ng.split("/")[-1] if ng.startswith("arn:aws:eks:") else ng
    ng_name = get_ng_name(ng)
    if action == "stop":
    eks.update_nodegroup_config(
  6. nrashok revised this gist Sep 12, 2025. 1 changed file with 31 additions and 7 deletions.
    38 changes: 31 additions & 7 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -17,6 +17,9 @@ Parameters:
    StartSize:
    Type: Number
    Default: 2
    MinSize:
    Type: Number
    Default: 1

    Resources:
    LambdaExecutionRole:
    @@ -33,10 +36,21 @@ Resources:
    ManagedPolicyArns:
    - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    - arn:aws:iam::aws:policy/AmazonEC2FullAccess
    - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
    - arn:aws:iam::aws:policy/AmazonEKSNodegroupPolicy
    Policies:
    - PolicyName: EKSCustomNodegroupScaling
    PolicyDocument:
    Version: '2012-10-17'
    Statement:
    - Effect: Allow
    Action:
    - eks:UpdateNodegroupConfig
    - eks:DescribeNodegroup
    Resource:
    - !Sub arn:aws:eks:${Region}:${AWS::AccountId}:nodegroup/${ClusterName}/*/*

    StopStartLambda:
    Type: AWS::Lambda::Function
    @@ -50,6 +64,7 @@ Resources:
    CLUSTER: !Ref ClusterName
    REGION: !Ref Region
    START_SIZE: !Ref StartSize
    MIN_SIZE: !Ref MinSize
    Code:
    ZipFile: |
    import boto3, os
    @@ -60,6 +75,7 @@ Resources:
    nodegroups = event.get("NodeGroups", [])
    instances = event.get("InstanceIds", [])
    start_size = int(event.get("StartSize", os.environ.get("START_SIZE", "2")))
    min_size = int(event.get("MinSize", os.environ.get("MIN_SIZE", "1")))
    region = event.get("Region", os.environ.get("REGION", "ap-south-1"))
    ec2 = boto3.client("ec2", region_name=region)
    @@ -74,20 +90,25 @@ Resources:
    # Scale managed nodegroups
    for ng in nodegroups:
    # If ARN, extract name
    ng_name = ng.split("/")[-1] if ng.startswith("arn:aws:eks:") else ng
    if action == "stop":
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={"desiredSize": 0}
    scalingConfig={
    "minSize": 0,
    "desiredSize": 0
    }
    )
    elif action == "start":
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={"desiredSize": start_size}
    scalingConfig={
    "minSize": min_size,
    "desiredSize": start_size
    }
    )
    return {
    @@ -173,7 +194,8 @@ Resources:
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "Region": "ap-south-1",
    "StartSize": 2
    "StartSize": 2,
    "MinSize": 1
    }
    ManualTriggerSSMDocument:
    @@ -219,7 +241,9 @@ Resources:
    "Action": event["Action"],
    "ClusterName": event["ClusterName"],
    "InstanceIds": event["InstanceIds"],
    "NodeGroups": event["NodeGroups"]
    "NodeGroups": event["NodeGroups"],
    "MinSize": event.get("MinSize", 1),
    "StartSize": event.get("StartSize", 2)
    })
    )
    return response
  7. nrashok revised this gist Sep 12, 2025. No changes.
  8. nrashok revised this gist Sep 12, 2025. 1 changed file with 28 additions and 36 deletions.
    64 changes: 28 additions & 36 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    AWSTemplateFormatVersion: '2010-09-09'
    Description: Automate stopping EC2 and self-managed EKS NodeGroups with Step Functions + EventBridge + SSM
    Description: Automate stopping/starting EC2 and managed EKS NodeGroups with Step Functions + EventBridge + SSM

    Parameters:
    InstanceIds:
    @@ -8,8 +8,8 @@ Parameters:
    ClusterName:
    Type: String
    Default: mr-blues-dev
    NodeGroupName:
    Type: String
    NodeGroups:
    Type: CommaDelimitedList
    Default: t3-spot-private
    Region:
    Type: String
    @@ -37,7 +37,6 @@ Resources:
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AutoScalingFullAccess

    StopStartLambda:
    Type: AWS::Lambda::Function
    @@ -46,19 +45,25 @@ Resources:
    Handler: index.lambda_handler
    Role: !GetAtt LambdaExecutionRole.Arn
    Timeout: 300
    Environment:
    Variables:
    CLUSTER: !Ref ClusterName
    REGION: !Ref Region
    START_SIZE: !Ref StartSize
    Code:
    ZipFile: |
    import boto3, os
    def lambda_handler(event, context):
    action = event.get("Action")
    instances = event.get("InstanceIds", [])
    cluster_name = event.get("ClusterName", os.environ.get("CLUSTER"))
    nodegroups = event.get("NodeGroups", [])
    instances = event.get("InstanceIds", [])
    start_size = int(event.get("StartSize", os.environ.get("START_SIZE", "2")))
    region = event.get("Region", os.environ.get("REGION", "ap-south-1"))
    ec2 = boto3.client('ec2', region_name=region)
    asg_client = boto3.client('autoscaling', region_name=region)
    ec2 = boto3.client("ec2", region_name=region)
    eks = boto3.client("eks", region_name=region)
    # Stop/start EC2 instances
    if instances:
    @@ -67,35 +72,22 @@ Resources:
    elif action == "start":
    ec2.start_instances(InstanceIds=instances)
    # Scale self-managed nodegroups via ASG
    for ng_name in nodegroups:
    # Auto-discover the ASG associated with nodegroup
    response = asg_client.describe_auto_scaling_groups()
    asg_name = None
    for asg in response['AutoScalingGroups']:
    for tag in asg.get('Tags', []):
    if tag['Key'] == 'eks:nodegroup-name' and tag['Value'] == ng_name:
    asg_name = asg['AutoScalingGroupName']
    break
    if asg_name:
    break
    if not asg_name:
    raise Exception(f"ASG for nodegroup {ng_name} not found in region {region}")
    # Scale managed nodegroups
    for ng in nodegroups:
    # If ARN, extract name
    ng_name = ng.split("/")[-1] if ng.startswith("arn:aws:eks:") else ng
    if action == "stop":
    asg_client.update_auto_scaling_group(
    AutoScalingGroupName=asg_name,
    MinSize=0,
    MaxSize=0,
    DesiredCapacity=0
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={"desiredSize": 0}
    )
    elif action == "start":
    asg_client.update_auto_scaling_group(
    AutoScalingGroupName=asg_name,
    MinSize=start_size,
    MaxSize=start_size,
    DesiredCapacity=start_size
    eks.update_nodegroup_config(
    clusterName=cluster_name,
    nodegroupName=ng_name,
    scalingConfig={"desiredSize": start_size}
    )
    return {
    @@ -131,7 +123,7 @@ Resources:
    DefinitionString:
    Fn::Sub: |
    {
    "Comment": "Stop/Start EC2 and self-managed EKS NodeGroups",
    "Comment": "Stop/Start EC2 and managed EKS NodeGroups",
    "StartAt": "StopStartAction",
    "States": {
    "StopStartAction": {
    @@ -190,7 +182,7 @@ Resources:
    DocumentType: Automation
    Content:
    schemaVersion: '0.3'
    description: "Manually trigger stop/start for EC2 + self-managed EKS NodeGroups"
    description: "Manually trigger stop/start for EC2 + managed EKS NodeGroups"
    parameters:
    Action:
    type: String
    @@ -202,7 +194,7 @@ Resources:
    default: ["i-07c33ff5a82b668cc","i-047a90a37986a669e"]
    NodeGroups:
    type: StringList
    description: "Self-managed NodeGroup names (auto-discovered ASG)"
    description: "Managed NodeGroup names or ARNs"
    default: ["t3-spot-private"]
    ClusterName:
    type: String
    @@ -243,7 +235,7 @@ Outputs:
    Description: ARN of the Step Function
    Value: !Ref AutomationStateMachine
    LambdaName:
    Description: Lambda function handling EC2/self-managed EKS actions
    Description: Lambda function handling EC2/managed EKS actions
    Value: !Ref StopStartLambda
    ManualSSMDocumentName:
    Description: Name of the SSM document for manual trigger
  9. nrashok revised this gist Sep 12, 2025. No changes.
  10. nrashok revised this gist Sep 12, 2025. 1 changed file with 94 additions and 62 deletions.
    156 changes: 94 additions & 62 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    AWSTemplateFormatVersion: '2010-09-09'
    Description: Automate stopping EC2 instances and scaling EKS NodeGroup using Lambda, Step Functions, SSM, and EventBridge
    Description: Automate stopping EC2 and self-managed EKS NodeGroups with Step Functions + EventBridge + SSM

    Parameters:
    InstanceIds:
    @@ -14,6 +14,9 @@ Parameters:
    Region:
    Type: String
    Default: ap-south-1
    StartSize:
    Type: Number
    Default: 2

    Resources:
    LambdaExecutionRole:
    @@ -32,10 +35,9 @@ Resources:
    - arn:aws:iam::aws:policy/AmazonEC2FullAccess
    - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
    - arn:aws:iam::aws:policy/AmazonEKSServicePolicy
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AutoScalingFullAccess

    StopStartLambda:
    Type: AWS::Lambda::Function
    @@ -46,35 +48,62 @@ Resources:
    Timeout: 300
    Code:
    ZipFile: |
    import boto3
    import boto3, os
    def lambda_handler(event, context):
    action = event.get("Action")
    instances = event.get("InstanceIds", [])
    cluster = event.get("ClusterName")
    nodegroups = event.get("NodeGroups", [])
    start_size = int(event.get("StartSize", os.environ.get("START_SIZE", "2")))
    region = event.get("Region", os.environ.get("REGION", "ap-south-1"))
    ec2 = boto3.client('ec2')
    eks = boto3.client('eks')
    ec2 = boto3.client('ec2', region_name=region)
    asg_client = boto3.client('autoscaling', region_name=region)
    if action == "stop":
    if instances:
    # Stop/start EC2 instances
    if instances:
    if action == "stop":
    ec2.stop_instances(InstanceIds=instances)
    for ng in nodegroups:
    eks.update_nodegroup_config(
    clusterName=cluster,
    nodegroupName=ng,
    scalingConfig={"minSize": 0, "maxSize": 0, "desiredSize": 0}
    )
    elif action == "start":
    if instances:
    elif action == "start":
    ec2.start_instances(InstanceIds=instances)
    for ng in nodegroups:
    eks.update_nodegroup_config(
    clusterName=cluster,
    nodegroupName=ng,
    scalingConfig={"minSize": 1, "maxSize": 3, "desiredSize": 1}
    # Scale self-managed nodegroups via ASG
    for ng_name in nodegroups:
    # Auto-discover the ASG associated with nodegroup
    response = asg_client.describe_auto_scaling_groups()
    asg_name = None
    for asg in response['AutoScalingGroups']:
    for tag in asg.get('Tags', []):
    if tag['Key'] == 'eks:nodegroup-name' and tag['Value'] == ng_name:
    asg_name = asg['AutoScalingGroupName']
    break
    if asg_name:
    break
    if not asg_name:
    raise Exception(f"ASG for nodegroup {ng_name} not found in region {region}")
    if action == "stop":
    asg_client.update_auto_scaling_group(
    AutoScalingGroupName=asg_name,
    MinSize=0,
    MaxSize=0,
    DesiredCapacity=0
    )
    elif action == "start":
    asg_client.update_auto_scaling_group(
    AutoScalingGroupName=asg_name,
    MinSize=start_size,
    MaxSize=start_size,
    DesiredCapacity=start_size
    )
    return {"status": "success", "action": action}
    return {
    "status": "success",
    "action": action,
    "instances": instances,
    "nodegroups": nodegroups
    }
    StepFunctionRole:
    Type: AWS::IAM::Role
    @@ -102,7 +131,7 @@ Resources:
    DefinitionString:
    Fn::Sub: |
    {
    "Comment": "Stop/Start EC2 and EKS NodeGroup",
    "Comment": "Stop/Start EC2 and self-managed EKS NodeGroups",
    "StartAt": "StopStartAction",
    "States": {
    "StopStartAction": {
    @@ -118,13 +147,50 @@ Resources:
    }
    }

    StopScheduleRule:
    Type: AWS::Events::Rule
    Properties:
    ScheduleExpression: cron(0 16 * * ? *) # 9:30 PM IST
    State: ENABLED
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StopTarget
    RoleArn: !GetAtt StepFunctionRole.Arn
    Input: |
    {
    "Action": "stop",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "Region": "ap-south-1"
    }
    StartScheduleRule:
    Type: AWS::Events::Rule
    Properties:
    ScheduleExpression: cron(0 2 * * ? *) # 7:30 AM IST
    State: ENABLED
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StartTarget
    RoleArn: !GetAtt StepFunctionRole.Arn
    Input: |
    {
    "Action": "start",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"],
    "Region": "ap-south-1",
    "StartSize": 2
    }
    ManualTriggerSSMDocument:
    Type: AWS::SSM::Document
    Properties:
    DocumentType: Automation
    Content:
    schemaVersion: '0.3'
    description: "Manually trigger stop/start for EC2 + EKS NodeGroups"
    description: "Manually trigger stop/start for EC2 + self-managed EKS NodeGroups"
    parameters:
    Action:
    type: String
    @@ -136,7 +202,7 @@ Resources:
    default: ["i-07c33ff5a82b668cc","i-047a90a37986a669e"]
    NodeGroups:
    type: StringList
    description: "EKS NodeGroup names to scale"
    description: "Self-managed NodeGroup names (auto-discovered ASG)"
    default: ["t3-spot-private"]
    ClusterName:
    type: String
    @@ -172,46 +238,12 @@ Resources:
    NodeGroups: "{{NodeGroups}}"
    StepFunctionArn: !Ref AutomationStateMachine

    StopScheduleRule:
    Type: AWS::Events::Rule
    Properties:
    ScheduleExpression: cron(0 16 * * ? *) # 9:30 PM IST
    State: ENABLED
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StopTarget
    RoleArn: !GetAtt StepFunctionRole.Arn
    Input: |
    {
    "Action": "stop",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"]
    }
    StartScheduleRule:
    Type: AWS::Events::Rule
    Properties:
    ScheduleExpression: cron(0 2 * * ? *) # 7:30 AM IST
    State: ENABLED
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StartTarget
    RoleArn: !GetAtt StepFunctionRole.Arn
    Input: |
    {
    "Action": "start",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"]
    }
    Outputs:
    StepFunctionArn:
    Description: ARN of the Step Function
    Value: !Ref AutomationStateMachine
    LambdaName:
    Description: Lambda function handling EC2/EKS actions
    Description: Lambda function handling EC2/self-managed EKS actions
    Value: !Ref StopStartLambda
    ManualSSMDocumentName:
    Description: Name of the SSM document for manual trigger
  11. nrashok revised this gist Sep 12, 2025. 1 changed file with 22 additions and 3 deletions.
    25 changes: 22 additions & 3 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -76,10 +76,29 @@ Resources:
    )
    return {"status": "success", "action": action}
    StepFunctionRole:
    Type: AWS::IAM::Role
    Properties:
    AssumeRolePolicyDocument:
    Version: '2012-10-17'
    Statement:
    - Effect: Allow
    Principal:
    Service: states.amazonaws.com
    Action: sts:AssumeRole
    Policies:
    - PolicyName: InvokeLambdaPolicy
    PolicyDocument:
    Version: '2012-10-17'
    Statement:
    - Effect: Allow
    Action: lambda:InvokeFunction
    Resource: !GetAtt StopStartLambda.Arn

    AutomationStateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
    RoleArn: !GetAtt LambdaExecutionRole.Arn
    RoleArn: !GetAtt StepFunctionRole.Arn
    DefinitionString:
    Fn::Sub: |
    {
    @@ -161,7 +180,7 @@ Resources:
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StopTarget
    RoleArn: !GetAtt LambdaExecutionRole.Arn
    RoleArn: !GetAtt StepFunctionRole.Arn
    Input: |
    {
    "Action": "stop",
    @@ -178,7 +197,7 @@ Resources:
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StartTarget
    RoleArn: !GetAtt LambdaExecutionRole.Arn
    RoleArn: !GetAtt StepFunctionRole.Arn
    Input: |
    {
    "Action": "start",
  12. nrashok created this gist Sep 12, 2025.
    199 changes: 199 additions & 0 deletions ashok-od-cn-cf-v4.yaml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,199 @@
    AWSTemplateFormatVersion: '2010-09-09'
    Description: Automate stopping EC2 instances and scaling EKS NodeGroup using Lambda, Step Functions, SSM, and EventBridge

    Parameters:
    InstanceIds:
    Type: CommaDelimitedList
    Default: i-07c33ff5a82b668cc,i-047a90a37986a669e
    ClusterName:
    Type: String
    Default: mr-blues-dev
    NodeGroupName:
    Type: String
    Default: t3-spot-private
    Region:
    Type: String
    Default: ap-south-1

    Resources:
    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
    - arn:aws:iam::aws:policy/AmazonEC2FullAccess
    - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
    - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
    - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
    - arn:aws:iam::aws:policy/AmazonEKSServicePolicy
    - arn:aws:iam::aws:policy/AmazonSSMFullAccess
    - arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess

    StopStartLambda:
    Type: AWS::Lambda::Function
    Properties:
    Runtime: python3.9
    Handler: index.lambda_handler
    Role: !GetAtt LambdaExecutionRole.Arn
    Timeout: 300
    Code:
    ZipFile: |
    import boto3
    def lambda_handler(event, context):
    action = event.get("Action")
    instances = event.get("InstanceIds", [])
    cluster = event.get("ClusterName")
    nodegroups = event.get("NodeGroups", [])
    ec2 = boto3.client('ec2')
    eks = boto3.client('eks')
    if action == "stop":
    if instances:
    ec2.stop_instances(InstanceIds=instances)
    for ng in nodegroups:
    eks.update_nodegroup_config(
    clusterName=cluster,
    nodegroupName=ng,
    scalingConfig={"minSize": 0, "maxSize": 0, "desiredSize": 0}
    )
    elif action == "start":
    if instances:
    ec2.start_instances(InstanceIds=instances)
    for ng in nodegroups:
    eks.update_nodegroup_config(
    clusterName=cluster,
    nodegroupName=ng,
    scalingConfig={"minSize": 1, "maxSize": 3, "desiredSize": 1}
    )
    return {"status": "success", "action": action}
    AutomationStateMachine:
    Type: AWS::StepFunctions::StateMachine
    Properties:
    RoleArn: !GetAtt LambdaExecutionRole.Arn
    DefinitionString:
    Fn::Sub: |
    {
    "Comment": "Stop/Start EC2 and EKS NodeGroup",
    "StartAt": "StopStartAction",
    "States": {
    "StopStartAction": {
    "Type": "Task",
    "Resource": "arn:aws:states:::lambda:invoke",
    "OutputPath": "$.Payload",
    "Parameters": {
    "FunctionName": "${StopStartLambda}",
    "Payload.$": "$"
    },
    "End": true
    }
    }
    }

    ManualTriggerSSMDocument:
    Type: AWS::SSM::Document
    Properties:
    DocumentType: Automation
    Content:
    schemaVersion: '0.3'
    description: "Manually trigger stop/start for EC2 + EKS NodeGroups"
    parameters:
    Action:
    type: String
    description: "Action to perform (stop/start)"
    allowedValues: ["stop", "start"]
    InstanceIds:
    type: StringList
    description: "List of EC2 instance IDs to start/stop"
    default: ["i-07c33ff5a82b668cc","i-047a90a37986a669e"]
    NodeGroups:
    type: StringList
    description: "EKS NodeGroup names to scale"
    default: ["t3-spot-private"]
    ClusterName:
    type: String
    description: "EKS Cluster name"
    default: "mr-blues-dev"
    StepFunctionArn:
    type: String
    description: "Step Function ARN"
    mainSteps:
    - name: InvokeStepFunction
    action: aws:executeScript
    inputs:
    Runtime: python3.8
    Handler: handler
    Script: |
    import boto3, json
    def handler(event, context):
    sfn = boto3.client("stepfunctions")
    response = sfn.start_execution(
    stateMachineArn=event["StepFunctionArn"],
    input=json.dumps({
    "Action": event["Action"],
    "ClusterName": event["ClusterName"],
    "InstanceIds": event["InstanceIds"],
    "NodeGroups": event["NodeGroups"]
    })
    )
    return response
    InputPayload:
    Action: "{{Action}}"
    ClusterName: "{{ClusterName}}"
    InstanceIds: "{{InstanceIds}}"
    NodeGroups: "{{NodeGroups}}"
    StepFunctionArn: !Ref AutomationStateMachine

    StopScheduleRule:
    Type: AWS::Events::Rule
    Properties:
    ScheduleExpression: cron(0 16 * * ? *) # 9:30 PM IST
    State: ENABLED
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StopTarget
    RoleArn: !GetAtt LambdaExecutionRole.Arn
    Input: |
    {
    "Action": "stop",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"]
    }
    StartScheduleRule:
    Type: AWS::Events::Rule
    Properties:
    ScheduleExpression: cron(0 2 * * ? *) # 7:30 AM IST
    State: ENABLED
    Targets:
    - Arn: !Ref AutomationStateMachine
    Id: StartTarget
    RoleArn: !GetAtt LambdaExecutionRole.Arn
    Input: |
    {
    "Action": "start",
    "ClusterName": "mr-blues-dev",
    "InstanceIds": ["i-07c33ff5a82b668cc","i-047a90a37986a669e"],
    "NodeGroups": ["t3-spot-private"]
    }
    Outputs:
    StepFunctionArn:
    Description: ARN of the Step Function
    Value: !Ref AutomationStateMachine
    LambdaName:
    Description: Lambda function handling EC2/EKS actions
    Value: !Ref StopStartLambda
    ManualSSMDocumentName:
    Description: Name of the SSM document for manual trigger
    Value: !Ref ManualTriggerSSMDocument