github.com/in4it/ecs-deploy@v0.0.42-0.20240508120354-ed77ff16df25/provider/ecs/autoscaling.go (about) 1 package ecs 2 3 import ( 4 "github.com/aws/aws-sdk-go/aws" 5 "github.com/aws/aws-sdk-go/aws/awserr" 6 "github.com/aws/aws-sdk-go/aws/session" 7 "github.com/aws/aws-sdk-go/service/applicationautoscaling" 8 "github.com/aws/aws-sdk-go/service/autoscaling" 9 "github.com/in4it/ecs-deploy/service" 10 "github.com/juju/loggo" 11 12 "encoding/base64" 13 "errors" 14 "strings" 15 ) 16 17 // logging 18 var autoscalingLogger = loggo.GetLogger("autoscaling") 19 20 // ECR struct 21 type AutoScaling struct { 22 } 23 24 type AutoScalingIf interface { 25 GetAutoScalingGroupByTag(clusterName string) (string, error) 26 ScaleClusterNodes(autoScalingGroupName string, change int64) error 27 } 28 29 func (a *AutoScaling) CompleteLifecycleAction(autoScalingGroupName, instanceId, action, lifecycleHookName, lifecycleToken string) error { 30 svc := autoscaling.New(session.New()) 31 input := &autoscaling.CompleteLifecycleActionInput{ 32 AutoScalingGroupName: aws.String(autoScalingGroupName), 33 InstanceId: aws.String(instanceId), 34 LifecycleActionResult: aws.String(action), 35 LifecycleActionToken: aws.String(lifecycleToken), 36 LifecycleHookName: aws.String(lifecycleHookName), 37 } 38 39 _, err := svc.CompleteLifecycleAction(input) 40 if err != nil { 41 if aerr, ok := err.(awserr.Error); ok { 42 ecsLogger.Errorf("%v", aerr.Error()) 43 } else { 44 ecsLogger.Errorf("%v", err.Error()) 45 } 46 return err 47 } 48 return nil 49 } 50 func (a *AutoScaling) CompletePendingLifecycleAction(autoScalingGroupName, instanceId, action, lifecycleHookName string) error { 51 svc := autoscaling.New(session.New()) 52 input := &autoscaling.CompleteLifecycleActionInput{ 53 AutoScalingGroupName: aws.String(autoScalingGroupName), 54 InstanceId: aws.String(instanceId), 55 LifecycleActionResult: aws.String(action), 56 LifecycleHookName: aws.String(lifecycleHookName), 57 } 58 59 autoscalingLogger.Debugf("Running CompleteLifecycleAction with input: %+v", input) 60 61 _, err := svc.CompleteLifecycleAction(input) 62 if err != nil { 63 if aerr, ok := err.(awserr.Error); ok { 64 ecsLogger.Errorf("%v", aerr.Error()) 65 } else { 66 ecsLogger.Errorf("%v", err.Error()) 67 } 68 return err 69 } 70 return nil 71 } 72 func (a *AutoScaling) GetLifecycleHookNames(autoScalingGroupName, lifecycleHookType string) ([]string, error) { 73 var lifecycleHookNames []string 74 svc := autoscaling.New(session.New()) 75 input := &autoscaling.DescribeLifecycleHooksInput{ 76 AutoScalingGroupName: aws.String(autoScalingGroupName), 77 } 78 79 result, err := svc.DescribeLifecycleHooks(input) 80 if err != nil { 81 if aerr, ok := err.(awserr.Error); ok { 82 ecsLogger.Errorf("%v", aerr.Error()) 83 } else { 84 ecsLogger.Errorf("%v", err.Error()) 85 } 86 return lifecycleHookNames, err 87 } 88 if len(result.LifecycleHooks) == 0 { 89 return lifecycleHookNames, errors.New("No life cycle hooks returned") 90 } 91 for _, v := range result.LifecycleHooks { 92 if aws.StringValue(v.LifecycleTransition) == lifecycleHookType { 93 lifecycleHookNames = append(lifecycleHookNames, aws.StringValue(v.LifecycleHookName)) 94 } 95 } 96 return lifecycleHookNames, nil 97 } 98 99 func (a *AutoScaling) CreateLaunchConfiguration(clusterName string, keyName string, instanceType string, instanceProfile string, securitygroups []string) error { 100 ecs := ECS{} 101 svc := autoscaling.New(session.New()) 102 amiId, err := ecs.GetECSAMI() 103 if err != nil { 104 return err 105 } 106 input := &autoscaling.CreateLaunchConfigurationInput{ 107 IamInstanceProfile: aws.String(instanceProfile), 108 ImageId: aws.String(amiId), 109 InstanceType: aws.String(instanceType), 110 KeyName: aws.String(keyName), 111 LaunchConfigurationName: aws.String(clusterName), 112 SecurityGroups: aws.StringSlice(securitygroups), 113 UserData: aws.String(base64.StdEncoding.EncodeToString([]byte("#!/bin/bash\necho 'ECS_CLUSTER=" + clusterName + "' > /etc/ecs/ecs.config\nstart ecs\n"))), 114 } 115 ecsLogger.Debugf("createLaunchConfiguration with: %+v", input) 116 _, err = svc.CreateLaunchConfiguration(input) 117 if err != nil { 118 if aerr, ok := err.(awserr.Error); ok { 119 if strings.Contains(aerr.Message(), "Invalid IamInstanceProfile") { 120 ecsLogger.Debugf("Caught RetryableError: %v", aerr.Message()) 121 return errors.New("RetryableError: Invalid IamInstanceProfile") 122 } else { 123 ecsLogger.Errorf("%v", aerr.Error()) 124 } 125 } else { 126 ecsLogger.Errorf("%v", err.Error()) 127 } 128 return err 129 } 130 return nil 131 } 132 func (a *AutoScaling) DeleteLaunchConfiguration(clusterName string) error { 133 svc := autoscaling.New(session.New()) 134 input := &autoscaling.DeleteLaunchConfigurationInput{ 135 LaunchConfigurationName: aws.String(clusterName), 136 } 137 _, err := svc.DeleteLaunchConfiguration(input) 138 if err != nil { 139 if aerr, ok := err.(awserr.Error); ok { 140 ecsLogger.Errorf("%v", aerr.Error()) 141 } else { 142 ecsLogger.Errorf("%v", err.Error()) 143 } 144 return err 145 } 146 return nil 147 } 148 func (a *AutoScaling) CreateAutoScalingGroup(clusterName string, desiredCapacity int64, maxSize int64, minSize int64, subnets []string) error { 149 svc := autoscaling.New(session.New()) 150 input := &autoscaling.CreateAutoScalingGroupInput{ 151 AutoScalingGroupName: aws.String(clusterName), 152 DesiredCapacity: aws.Int64(desiredCapacity), 153 HealthCheckType: aws.String("EC2"), 154 LaunchConfigurationName: aws.String(clusterName), 155 MaxSize: aws.Int64(maxSize), 156 MinSize: aws.Int64(minSize), 157 Tags: []*autoscaling.Tag{ 158 {Key: aws.String("Name"), Value: aws.String("ecs-" + clusterName), PropagateAtLaunch: aws.Bool(true)}, 159 {Key: aws.String("Cluster"), Value: aws.String(clusterName), PropagateAtLaunch: aws.Bool(true)}, 160 }, 161 TerminationPolicies: []*string{aws.String("OldestLaunchConfiguration"), aws.String("Default")}, 162 VPCZoneIdentifier: aws.String(strings.Join(subnets, ",")), 163 } 164 _, err := svc.CreateAutoScalingGroup(input) 165 if err != nil { 166 if aerr, ok := err.(awserr.Error); ok { 167 ecsLogger.Errorf("%v", aerr.Error()) 168 } else { 169 ecsLogger.Errorf("%v", err.Error()) 170 } 171 return err 172 } 173 return nil 174 } 175 func (a *AutoScaling) WaitForAutoScalingGroupInService(clusterName string) error { 176 svc := autoscaling.New(session.New()) 177 input := &autoscaling.DescribeAutoScalingGroupsInput{ 178 AutoScalingGroupNames: []*string{aws.String(clusterName)}, 179 } 180 err := svc.WaitUntilGroupInService(input) 181 if err != nil { 182 if aerr, ok := err.(awserr.Error); ok { 183 ecsLogger.Errorf("%v", aerr.Error()) 184 } else { 185 ecsLogger.Errorf("%v", err.Error()) 186 } 187 return err 188 } 189 return nil 190 } 191 func (a *AutoScaling) WaitForAutoScalingGroupNotExists(clusterName string) error { 192 svc := autoscaling.New(session.New()) 193 input := &autoscaling.DescribeAutoScalingGroupsInput{ 194 AutoScalingGroupNames: []*string{aws.String(clusterName)}, 195 } 196 err := svc.WaitUntilGroupNotExists(input) 197 if err != nil { 198 if aerr, ok := err.(awserr.Error); ok { 199 ecsLogger.Errorf("%v", aerr.Error()) 200 } else { 201 ecsLogger.Errorf("%v", err.Error()) 202 } 203 return err 204 } 205 return nil 206 } 207 func (a *AutoScaling) DeleteAutoScalingGroup(clusterName string, forceDelete bool) error { 208 svc := autoscaling.New(session.New()) 209 input := &autoscaling.DeleteAutoScalingGroupInput{ 210 AutoScalingGroupName: aws.String(clusterName), 211 ForceDelete: aws.Bool(forceDelete), 212 } 213 _, err := svc.DeleteAutoScalingGroup(input) 214 if err != nil { 215 if aerr, ok := err.(awserr.Error); ok { 216 ecsLogger.Errorf("%v", aerr.Error()) 217 } else { 218 ecsLogger.Errorf("%v", err.Error()) 219 } 220 return err 221 } 222 return nil 223 } 224 func (a *AutoScaling) ScaleClusterNodes(autoScalingGroupName string, change int64) error { 225 minSize, desiredCapacity, maxSize, err := a.GetClusterNodeDesiredCount(autoScalingGroupName) 226 if err != nil { 227 return err 228 } 229 if change > 0 && desiredCapacity == maxSize { 230 return errors.New("Cluster is at maximum capacity") 231 } 232 if change < 0 && desiredCapacity == minSize { 233 return errors.New("Cluster is at minimum capacity") 234 } 235 236 svc := autoscaling.New(session.New()) 237 input := &autoscaling.UpdateAutoScalingGroupInput{ 238 AutoScalingGroupName: aws.String(autoScalingGroupName), 239 DesiredCapacity: aws.Int64(desiredCapacity + change), 240 } 241 _, err = svc.UpdateAutoScalingGroup(input) 242 if err != nil { 243 if aerr, ok := err.(awserr.Error); ok { 244 ecsLogger.Errorf("%v", aerr.Error()) 245 } else { 246 ecsLogger.Errorf("%v", err.Error()) 247 } 248 return err 249 } 250 return nil 251 } 252 func (a *AutoScaling) GetClusterNodeDesiredCount(autoScalingGroupName string) (int64, int64, int64, error) { 253 svc := autoscaling.New(session.New()) 254 input := &autoscaling.DescribeAutoScalingGroupsInput{ 255 AutoScalingGroupNames: []*string{aws.String(autoScalingGroupName)}, 256 } 257 result, err := svc.DescribeAutoScalingGroups(input) 258 if err != nil { 259 if aerr, ok := err.(awserr.Error); ok { 260 ecsLogger.Errorf("%v", aerr.Error()) 261 } else { 262 ecsLogger.Errorf("%v", err.Error()) 263 } 264 return 0, 0, 0, err 265 } 266 if len(result.AutoScalingGroups) == 0 { 267 return 0, 0, 0, errors.New("No autoscaling groups returned") 268 } 269 270 return aws.Int64Value(result.AutoScalingGroups[0].MinSize), 271 aws.Int64Value(result.AutoScalingGroups[0].DesiredCapacity), 272 aws.Int64Value(result.AutoScalingGroups[0].MaxSize), 273 nil 274 } 275 func (a *AutoScaling) GetAutoScalingGroupByTag(clusterName string) (string, error) { 276 var result string 277 svc := autoscaling.New(session.New()) 278 input := &autoscaling.DescribeAutoScalingGroupsInput{} 279 pageNum := 0 280 err := svc.DescribeAutoScalingGroupsPages(input, 281 func(page *autoscaling.DescribeAutoScalingGroupsOutput, lastPage bool) bool { 282 pageNum++ 283 for _, v := range page.AutoScalingGroups { 284 for _, tag := range v.Tags { 285 if aws.StringValue(tag.Key) == "Cluster" && aws.StringValue(tag.Value) == clusterName { 286 result = aws.StringValue(v.AutoScalingGroupName) 287 } 288 } 289 } 290 return pageNum <= 100 291 }) 292 293 if err != nil { 294 if aerr, ok := err.(awserr.Error); ok { 295 ecsLogger.Errorf(aerr.Error()) 296 } else { 297 ecsLogger.Errorf(err.Error()) 298 } 299 } 300 if result == "" { 301 return result, errors.New("ClusterNotFound: Could not find cluster by tag key=Cluster,Value=" + clusterName) 302 } 303 return result, nil 304 } 305 306 func (a *AutoScaling) RegisterScalableTarget(minCapacity, maxCapacity int64, resourceId, roleArn string) error { 307 svc := applicationautoscaling.New(session.New()) 308 input := &applicationautoscaling.RegisterScalableTargetInput{ 309 MinCapacity: aws.Int64(minCapacity), 310 MaxCapacity: aws.Int64(maxCapacity), 311 ResourceId: aws.String(resourceId), // serviceName/clusterName/app 312 RoleARN: aws.String(roleArn), 313 ScalableDimension: aws.String("ecs:service:DesiredCount"), 314 ServiceNamespace: aws.String("ecs"), 315 } 316 _, err := svc.RegisterScalableTarget(input) 317 if err != nil { 318 if aerr, ok := err.(awserr.Error); ok { 319 ecsLogger.Errorf("%v", aerr.Error()) 320 } else { 321 ecsLogger.Errorf("%v", err.Error()) 322 } 323 return err 324 } 325 return nil 326 } 327 func (a *AutoScaling) DeregisterScalableTarget(resourceId string) error { 328 svc := applicationautoscaling.New(session.New()) 329 input := &applicationautoscaling.DeregisterScalableTargetInput{ 330 ResourceId: aws.String(resourceId), // serviceName/clusterName/app 331 ScalableDimension: aws.String("ecs:service:DesiredCount"), 332 ServiceNamespace: aws.String("ecs"), 333 } 334 _, err := svc.DeregisterScalableTarget(input) 335 if err != nil { 336 if aerr, ok := err.(awserr.Error); ok { 337 ecsLogger.Errorf("%v", aerr.Error()) 338 } else { 339 ecsLogger.Errorf("%v", err.Error()) 340 } 341 return err 342 } 343 return nil 344 } 345 func (a *AutoScaling) PutScalingPolicy(policyName, resourceId string, cooldown, scalingAdjustment int64) (string, error) { 346 svc := applicationautoscaling.New(session.New()) 347 input := &applicationautoscaling.PutScalingPolicyInput{ 348 PolicyName: aws.String(policyName), 349 PolicyType: aws.String("StepScaling"), 350 ResourceId: aws.String(resourceId), // serviceName/clusterName/app 351 ScalableDimension: aws.String("ecs:service:DesiredCount"), 352 ServiceNamespace: aws.String("ecs"), 353 StepScalingPolicyConfiguration: &applicationautoscaling.StepScalingPolicyConfiguration{ 354 AdjustmentType: aws.String("ChangeInCapacity"), 355 Cooldown: aws.Int64(cooldown), 356 StepAdjustments: []*applicationautoscaling.StepAdjustment{ 357 { 358 MetricIntervalLowerBound: aws.Float64(0), 359 ScalingAdjustment: aws.Int64(scalingAdjustment), 360 }, 361 }, 362 }, 363 } 364 result, err := svc.PutScalingPolicy(input) 365 if err != nil { 366 if aerr, ok := err.(awserr.Error); ok { 367 ecsLogger.Errorf("%v", aerr.Error()) 368 } else { 369 ecsLogger.Errorf("%v", err.Error()) 370 } 371 return "", err 372 } 373 return aws.StringValue(result.PolicyARN), nil 374 } 375 376 func (a *AutoScaling) DescribeScalableTargets(resourceIds []string) ([]service.Autoscaling, error) { 377 var as []service.Autoscaling 378 var scalableTargets []*applicationautoscaling.ScalableTarget 379 svc := applicationautoscaling.New(session.New()) 380 input := &applicationautoscaling.DescribeScalableTargetsInput{ 381 ResourceIds: aws.StringSlice(resourceIds), // serviceName/clusterName/app 382 ScalableDimension: aws.String("ecs:service:DesiredCount"), 383 ServiceNamespace: aws.String("ecs"), 384 } 385 pageNum := 0 386 err := svc.DescribeScalableTargetsPages(input, 387 func(page *applicationautoscaling.DescribeScalableTargetsOutput, lastPage bool) bool { 388 pageNum++ 389 scalableTargets = append(scalableTargets, page.ScalableTargets...) 390 return pageNum <= 100 391 }) 392 393 if err != nil { 394 if aerr, ok := err.(awserr.Error); ok { 395 ecsLogger.Errorf("%v", aerr.Error()) 396 } else { 397 ecsLogger.Errorf("%v", err.Error()) 398 } 399 return as, err 400 } 401 402 for _, v := range scalableTargets { 403 a := service.Autoscaling{} 404 a.MinimumCount = aws.Int64Value(v.MinCapacity) 405 a.MaximumCount = aws.Int64Value(v.MaxCapacity) 406 as = append(as, a) 407 } 408 409 return as, nil 410 } 411 func (a *AutoScaling) DescribeScalingPolicies(policyNames []string, resourceId string) ([]service.AutoscalingPolicy, error) { 412 var aps []service.AutoscalingPolicy 413 var scalingPolicies []*applicationautoscaling.ScalingPolicy 414 svc := applicationautoscaling.New(session.New()) 415 input := &applicationautoscaling.DescribeScalingPoliciesInput{ 416 PolicyNames: aws.StringSlice(policyNames), 417 ResourceId: aws.String(resourceId), // serviceName/clusterName/app 418 ScalableDimension: aws.String("ecs:service:DesiredCount"), 419 ServiceNamespace: aws.String("ecs"), 420 } 421 pageNum := 0 422 err := svc.DescribeScalingPoliciesPages(input, 423 func(page *applicationautoscaling.DescribeScalingPoliciesOutput, lastPage bool) bool { 424 pageNum++ 425 scalingPolicies = append(scalingPolicies, page.ScalingPolicies...) 426 return pageNum <= 100 427 }) 428 429 if err != nil { 430 if aerr, ok := err.(awserr.Error); ok { 431 ecsLogger.Errorf("%v", aerr.Error()) 432 } else { 433 ecsLogger.Errorf("%v", err.Error()) 434 } 435 return aps, err 436 } 437 438 for _, v := range scalingPolicies { 439 ap := service.AutoscalingPolicy{} 440 ap.PolicyName = aws.StringValue(v.PolicyName) 441 if len(v.StepScalingPolicyConfiguration.StepAdjustments) > 0 { 442 ap.ScalingAdjustment = aws.Int64Value(v.StepScalingPolicyConfiguration.StepAdjustments[0].ScalingAdjustment) 443 } 444 aps = append(aps, ap) 445 } 446 447 return aps, nil 448 } 449 450 func (a *AutoScaling) DeleteScalingPolicy(policyName, resourceId string) error { 451 svc := applicationautoscaling.New(session.New()) 452 453 input := &applicationautoscaling.DeleteScalingPolicyInput{ 454 PolicyName: aws.String(policyName), 455 ResourceId: aws.String(resourceId), // serviceName/clusterName/app 456 ScalableDimension: aws.String("ecs:service:DesiredCount"), 457 ServiceNamespace: aws.String("ecs"), 458 } 459 460 _, err := svc.DeleteScalingPolicy(input) 461 462 if err != nil { 463 if aerr, ok := err.(awserr.Error); ok { 464 ecsLogger.Errorf("%v", aerr.Error()) 465 } else { 466 ecsLogger.Errorf("%v", err.Error()) 467 } 468 return err 469 } 470 471 return nil 472 }