云形成自定义资源创建失败

问题描述

我正在尝试创建一个指向 lambda 函数自定义资源,然后调用它来生成随机优先级或我的 ELB 侦听器。

Lambda 函数代码如下。

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace aws_listenser_rule_priority_generator {
    public class Function {
        public async Task<int> FunctionHandler(FunctionParams input,ILambdaContext context) {
            AmazonElasticLoadBalancingV2Client elbV2Client = new AmazonElasticLoadBalancingV2Client(RegionEndpoint.EUWest1);
            var describeRulesResponse = await elbV2Client.DescribeRulesAsync(new DescribeRulesRequest  {
                ListenerArn = input.ListenerArn
            });
            var priority = 0;
            var random = new Random();
            do {
                priority = random.Next(1,50000);
            }
            while(describeRulesResponse.Rules.Exists(r => r.Priority == priority.ToString()));
            
            return priority;
        }
    }

    public class FunctionParams {
        public string ListenerArn { get; set; }
    }
}

我已经使用以下参数在 AWS 控制台上测试了这个 lambda,它成功返回。

{
  "ListenerArn": "arn:aws:elasticloadbalancing:eu-west-1:706137030892:listener/app/Cumulus/dfcf28e0393cbf77/cdfe928b0285d5f0"
}

但是一旦我尝试将其与 Cloud Formation 结合使用。自定义资源在创建过程中卡住。

Resources:
  ListenerPriority:
    Type: Custom::Number
    Properties:
      Servicetoken: "arn:aws:lambda:eu-west-1:706137030892:function:GenerateListenerPriority"
      ListenerArn: "arn:aws:elasticloadbalancing:eu-west-1:706137030892:listener/app/Cumulus/dfcf28e0393cbf77/cdfe928b0285d5f0"

解决方法

前一种方法的问题是数据格式。当我们使用自定义资源时,Cloud Formation 会以指定的格式发送请求,并且期望在指定的响应 URL 处异步响应。

我必须对代码进行以下更新:

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace aws_listenser_rule_priority_generator
{
    public class Function
    {
        public async Task FunctionHandler(CustomResourceRequest<CustomResourceRequestProperties> crfRequest,ILambdaContext context)
        {
            var jsonResponse = string.Empty;
            
            if(crfRequest.RequestType.ToUpperInvariant() != "CREATE") {
                jsonResponse = JsonSerializer.Serialize(new CustomResourceResponse<object> {
                    Status = "SUCCESS",Reason = string.Empty,PhysicalResourceId = Guid.NewGuid().ToString(),StackId = crfRequest.StackId,RequestId = crfRequest.RequestId,LogicalResourceId = crfRequest.LogicalResourceId,Data = new {
                        Dummy = "Dummy"
                    }
                });
            }
            else {
                AmazonElasticLoadBalancingV2Client elbV2Client = new AmazonElasticLoadBalancingV2Client(RegionEndpoint.EUWest1);
                var describeRulesResponse = await elbV2Client.DescribeRulesAsync(new DescribeRulesRequest  {
                    ListenerArn = crfRequest.ResourceProperties.ListenerArn
                });
                var priority = 0;
                var random = new Random();
                do {
                    priority = random.Next(1,50000);
                }
                while(describeRulesResponse.Rules.Exists(r => r.Priority == priority.ToString()));

                jsonResponse = JsonSerializer.Serialize(new CustomResourceResponse<object> {
                    Status = "SUCCESS",Data = new {
                        Priority = priority
                    }
                });
            }
            var byteArray = Encoding.UTF8.GetBytes(jsonResponse);
            
            var webRequest = WebRequest.Create(crfRequest.ResponseURL) as HttpWebRequest;
            webRequest.Method = "PUT";
            webRequest.ContentType = string.Empty;
            webRequest.ContentLength = byteArray.Length;
            
            using(Stream datastream = webRequest.GetRequestStream()) {
                await datastream.WriteAsync(byteArray,byteArray.Length);
            }

            await webRequest.GetResponseAsync();
        }
    }
    public class CustomResourceRequest<T> where T : ICustomResourceRequestProperties {
        public string RequestType { get; set; }
        public string ResponseURL { get; set; }
        public string StackId { get; set; }
        public string RequestId { get; set; }
        public string ResourceType { get; set; }
        public string LogicalResourceId{ get; set; }
        public string PhysicalResourceId { get; set; }
        public T ResourceProperties { get; set; }
        public T OldResourceProperties { get; set; }
    }
    public class CustomResourceResponse<T> {
        public string Status { get; set; }
        public string Reason { get; set; }
        public string PhysicalResourceId { get; set; }
        public string StackId { get; set; }
        public string RequestId { get; set; }
        public string LogicalResourceId { get; set; }
        public bool NoEcho { get; set; }
        public T Data { get; set; }
    }
    public interface ICustomResourceRequestProperties {
        public string ServiceToken { get; set; }
    }
    public class CustomResourceRequestProperties : ICustomResourceRequestProperties
    {
        string ICustomResourceRequestProperties.ServiceToken { get; set; }
        public string ListenerArn { get; set; }
    }
}

上面的代码希望一切正常,没有任何问题,并且没有 try catch 块。最佳做法是使用 try catch 块并发送失败响应。

可以参考以下网址了解更多详情: