用Terraform创建Lambda实现AWS中SDWAN实例的路由自动切换

用Terraform创建Lambda实现AWS中SDWAN实例的路由自动切换

背景

为了实现在AWS的一个VPC内部署两个SDWAN实例,并通过lambda实现双机热备和路由的自动切换,我之前写过如下博客。

为了能够通过“基础设施即代码”的思想,完成相关服务的快速部署和维护,本文尝试通过terraform对相关资源进行创建和管理。

操作步骤

准备步骤

确保当前环境已安装terraform,并创建指定文件夹并进入,然后创建main.tf文件并准备写入配置文件。

mkdir terraform-experiment4
cd terraform-experiment4
vim main.tf

注:下文中提及写入main.tf的内容,请逐一贴入文件内后,最后统一执行 terraform apply的指令。

创建SDWAN实例

实际生产环境中,我们会提供具有路由功能的SDWAN实例。

本测试环境中,我们通过terraform创建两个t3.micro的ubuntu实例用做测试,因为实例的AMI和类型,不影响lambda的路由倒换功能。

在main.tf中,通过如下代码创建两个SDWAN实例。

provider "aws" {
  access_key = "AKIA**********NU5"
  secret_key = "leVIw***************************wx/a"
  region = "ap-east-1"
}

# 创建名为"EC2-SDWAN-A"的EC2
resource "aws_instance" "EC2-SDWAN-A" {
  ami = "ami-03490b1b7425e5fe3"
  instance_type = "t3.micro"
  availability_zone = "ap-east-1a"
  tags = {
    Name = "SDWAN-A"
  }
}

# 创建名为"EC2-SDWAN-B"的EC2
resource "aws_instance" "EC2-SDWAN-B" {
  ami = "ami-03490b1b7425e5fe3"
  instance_type = "t3.micro"
  availability_zone = "ap-east-1b"
  tags = {
    Name = "SDWAN-B"
  }
}

创建lambda

在main.tf中,通过如下代码创建lambda

# 创建名为"terraform-managed-lambda_RouteTableFailover"的lambda
resource "aws_lambda_function" "terraform_lambda_RouteTableFailover" {
  function_name = "terraform-managed-lambda_RouteTableFailover"

  filename = "RouteTableFailover.zip"
  handler = "RouteTableFailover.lambda_handler"
  role    = "${aws_iam_role.iam_lambda_RouteTableFailover.arn}"
  runtime = "python3.9"
  timeout ="15"
  environment {
    variables = {
		region = "ap-east-1"
		vpcid = "vpc-0479a92f0dd5add79"
		host1eni = aws_instance.EC2-SDWAN-A.primary_network_interface_id
		host2eni = aws_instance.EC2-SDWAN-B.primary_network_interface_id
    }
  }  
}

这里RouteTableFailover.zip是把包含了路由倒换python代码的RouteTableFailover.py压缩为zip文件,并上传至当前terraform运行服务器的terraform-experiment4文件夹下。

环境变量中的vpcid 是已经创建好的VPC的ID;host1eni是SDWAN-A实例的网络接口ENI的ID,可通过terraform自动获取变量;host2eni与host1eni同理。

handler = “RouteTableFailover.lambda_handler” 代表RouteTableFailover这个文件下的lambda_handler为该lambda函数的入口点,它告诉 Lambda 在哪里开始执行代码。

创建IAM执行角色

在main.tf中,通过如下代码创建名为”iam_lambda_RouteTableFailover”的IAM执行角色,并创建信任关系

#创建名为"iam_lambda_RouteTableFailover"的IAM执行角色,并创建信任关系
resource "aws_iam_role" "iam_lambda_RouteTableFailover" {
  name = "iam_lambda_RouteTableFailover"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

上述指令生成的内容,在AWS控制台如下所示。

创建IAM Policy策略

在main.tf中,通过如下代码创建两个policy:

  • policy1,添加路由表相关操作的必要的权限,并绑定到当前IAM iam_lambda_RouteTableFailover 上;
  • policy2,添加lambda执行的基本权限,并绑定到当前IAM iam_lambda_RouteTableFailover 上;
# 创建policy1,添加路由表相关操作的必要的权限,并绑定到当前IAM iam_lambda_RouteTableFailover 上;
data "aws_iam_policy_document" "lambda_policy_1" {
  statement {
    effect = "Allow"

    actions = [
        "ec2:DescribeInstances",
        "ec2:DescribeTags",
        "ec2:DescribeVpcs",
        "ec2:DescribeRouteTables",
        "ec2:CreateRoute",
        "ec2:DeleteRoute",
        "ec2:ReplaceRoute"
    ]

    resources = [
      "*"
    ]
  }
}

resource "aws_iam_policy" "lambda_policy_1" {
  name        = "Policy-RouteTableFailover"
  description = "Policy for Lambda execution"
  policy      = data.aws_iam_policy_document.lambda_policy_1.json
}

resource "aws_iam_role_policy_attachment" "Policy-RouteTableFailover" {
  role       = aws_iam_role.iam_lambda_RouteTableFailover.name
  policy_arn = aws_iam_policy.lambda_policy_1.arn
}



# 创建policy2,添加lambda执行的基本权限,并绑定到当前IAM iam_lambda_RouteTableFailover 上;
data "aws_iam_policy_document" "lambda_policy_2" {
  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup"
    ]

    resources = [
      "arn:aws:logs:*"
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ]

    resources = [
      "arn:aws:logs:*"
    ]
  }
}

resource "aws_iam_policy" "lambda_policy_2" {
  name        = "Policy-AWSLambdaBasicExecutionRole"
  description = "Policy for Lambda execution"
  policy      = data.aws_iam_policy_document.lambda_policy_2.json
}

resource "aws_iam_role_policy_attachment" "Policy-AWSLambdaBasicExecutionRole" {
  role       = aws_iam_role.iam_lambda_RouteTableFailover.name
  policy_arn = aws_iam_policy.lambda_policy_2.arn
}

创建后,AWS控制台可见配置如下图。

创建lambda触发器

在main.tf中,通过如下代码创建触发lambda的触发器,用Cloudwatch的EventBridge触发lambda运行:当EC2-SDWAN-A或EC2-SDWAN-B的状态变为非运行状态时,触发EventBridge。

这部分代码还配置了Cloudwatch Events运行Lambda所必需的权限。

#创建触发lambda的触发器,用Cloudwatch的EventBridge触发lambda运行
#当EC2-SDWAN-A或EC2-SDWAN-B的状态变为非运行状态时,触发EventBridge
resource"aws_cloudwatch_event_rule""EventBridge_Rule_DetectSdwanState" {
  name                ="CloudWatch-Rule-Axesdn-DetectvAerState-ChangeRouteTableAuto"
  description         ="For triggering lambda AxesdnRouterTableAutoFailoverForSDWAN-vAER, when one of the vAERs‘ state is NOT running, to change route table automatically. Please do NOT delete it."
  
  event_pattern = jsonencode({
		  "source": [
			"aws.ec2"
		  ],
		  "detail-type": [
			"EC2 Instance State-change Notification"
		  ],
		  "detail": {
			"state": [
			  "pending",
			  "shutting-down",
			  "stopped",
			  "stopping",
			  "terminated"
			],
			"instance-id": [
			  "${aws_instance.EC2-SDWAN-A.id}",
			  "${aws_instance.EC2-SDWAN-B.id}"
			]
		  }
		})
}

resource"aws_cloudwatch_event_target""set_target" {
  rule  ="${aws_cloudwatch_event_rule.EventBridge_Rule_DetectSdwanState.name}"
  arn   ="${aws_lambda_function.terraform_lambda_RouteTableFailover.arn}"
}

data"aws_caller_identity""current" {
}

resource"aws_lambda_permission""allow_cloudwatch" {
  statement_id   ="AllowExecutionFromCloudWatch"
  action         ="lambda:InvokeFunction"
  function_name  ="${aws_lambda_function.terraform_lambda_RouteTableFailover.function_name}"
  principal      ="events.amazonaws.com"
  source_account ="${data.aws_caller_identity.current.account_id}"
  source_arn     ="${aws_cloudwatch_event_rule.EventBridge_Rule_DetectSdwanState.arn}"
}

用terraform创建资源

上述所有代码都写入main.tf文件中后,就可以运行

terraform init
terraform apply

来创建必要的资源了。

运行printout截图如下。

功能验证

完成配置后,可登陆AWS控制台,查看两个SDWAN实例已创建,如下图。

我们在该VPC默认路由表里手动创建一条静态路由:

当目的地址为10.0.0.0/24时,下一跳网关设置为实例SDWAN-A,如下图,

完成配置后,可见路由如下图,

其中,“目标”(也就是下一跳网关)显示为实例SDWAN-A的网络接口ID(ENI)。

根据我们设计的功能,当SDWAN-A实例状态不为running时,CloudWatch的EventBridge会触发lambda,查找路由表中下一跳网关为SDWAN-A的相关路由,并把这些下一跳网关改为SDWAN-B。

下面我们开始验证。

把SDWAN-A实例状态改为停止。

此时我们刷新路由表,可见相关路由表的“目标”改变了,如下图,

变更后的“目标”,即为SDWAN-B的网络接口ID,

还可到lambda页面的监控页中,点击“查看CloudWatch Logs”,查看相关log。

通过上述验证,可见自动倒换路由表的功能,可以正常实现。

此时若恢复启动SDWAN-A,路由表不变;

然后再把SDWAN-B实例状态变为停止,则相关路由自动倒换至SDWAN-A。这里不再提供截图。

总结

通过上面的配置,我们成功实现了,通过terraform的声明式的方式,创建了lambda函数,并创建了必要的IAM role和policy,以及lambda的trigger,从而实现:

当某个作为路由表下一跳网关的SDWAN实例的状态不正常时,CloudWatch的EventBridge触发lambda,运行python脚本,把相关路由倒换到另一个正常的SDWAN实例,从而实现SDWAN实例的冗余和自动倒换。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注