[DevOps云实践] 跨AWS账户及Region调用Lambda
本文將幫大家理清一下幾個問題:
- 如何跨不同AWS賬戶,不同
- Region來調用Lambda? 不同Lambda之間如何互相調用?
有時我們希望我們的Lambda脚本能夠運行在多個AWS賬戶中的不同Region下,但是,我們還不希望每個下面都去建立一個運行脚本邏輯的lambda。 下面我們會一步一步講解如何實現跨不同賬戶和區域去執行Lambda Function,包括:
- S3 bucket創建
- IAM Roles / policies 創建
- SSM Parameter 創建
- Lambda Functions 創建
1. Structure
简单来讲,我们将创建一个 invoke_master
的 Lambda,它会异步调用一个 invoke_slave
的lambda,并向其传递其他AWS账户需要的IAM Role ARN,从而,使得 invoke_slave
能够Assume到那个Role上进行必要的操作。
其调用流程如下图:
2. 创建 S3 bucket
首先,创建一个S3 Bucket,用来存储Lambda的运行结果。 在本例中,我们将把不同AWS账户的执行结果写入到各自的一个文件中。 创建命令如下:
aws s3 mb s3://<bucket_name_here> --region us-east-1
3. 创建IAM Roles 和 policies
我们需要创建2个IAM Role在主调用者的AWS账户上(假设aws账户id:123456789012),在每个被调用的AWS账户中创建一个IAM Role(假设id是:111111111,22222222…)。
- 主调用账户中创建一个名为:
lambda_functions_invoker
Role,并将把它赋予给invoke_master
lambda。 为lambda_functions_invoker
Role添加以下 2个Policy:
- AWS managed policy: AWSLambdaBasicExecutionRole
- Inline policy: (请用以下json内容创建一个policy)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "lambda:InvokeFunction", "lambda:InvokeAsync" ], "Resource": "*" }, { "Effect": "Allow", "Action": "ssm:GetParameter", "Resource": "arn:aws:ssm:*:123456789012:parameter/rolearnlist" } ] }
这个Policy用来允许Lambda调用其他lambda函数,并且能够从SSM中获取参数“rolearnlist
”。
请替换“123456789012”为你的主 AWS account id。
- 在主账户中为Lambda:
invoke_slave
创建一个Role:lambda_basicexec_crossaccount
,添加以下 2个Policy:
- AWS managed policy: AWSLambdaBasicExecutionRole
- Inline policy: (请用以下json内容创建一个policy)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject" ], "Resource": "arn:aws:s3:::your_s3_bucket_name_here/*" }, { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::111111111111:role/lambda-cross-account-access-role" }, { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::222222222222:role/lambda-cross-account-access-role" } ] }
以上inline policy使得lambda能够进行 STS assume role action,即能够Assume其他被调用AWS账户中的IAM Role而进行操作。 同时,赋予之前创建的Bucket的可写入文件的权限。
替换111111111111/222222222222 为目标aws account id
- 最后,需要登录各个目标(111111111111/222222222222 )账户中,创建IAM Role:
lambda-cross-account-access-role
在创建时候,其中的trusted entity
需要设置为主账户的ID,同时,为其加入lambda操作需要的必要权限。比如,本例子将会读取EC2一览,所以,需要添加policy:“AmazonEC2ReadOnlyAccess”
。
4. 创建 SSM Parameter
该参数中将包含所有目标账户的IAM Role的ARN。
登录到主账户中,进入服务:【 Systems Manager】,点击“Parameter Store” -> “Create Parameter”然后创建如下参数:
- Name: “rolearnlist”,
- Type: “StringList”,
- Value: “arn:aws:iam::111111111111:role/lambda-cross-account-access-role,arn:aws:iam::222222222222:role/lambda-cross-account-access-role”
5. 创建 Lambda Function
在主AWS账户中,我们将创建2个Lambda,一个是:invoke_master
, 另一个是:invoke_slave
。
Lambda使用的语言是:python3.6。
- 创建Lambda:invoke_master
用下面代码创建lambda,并在创建时候,将IAM Role
lambda_functions_invoker
赋予给它。
invoke_master
Lambda source code:
import boto3
import json
client = boto3.client('lambda')
ssm_client = boto3.client('ssm')
def lambda_handler(event, context):
# -----------------------------------------------------------------------
# Get the list of ARNs of cross-account IAM roles saved in SSM Parameter rolearnlist
# -----------------------------------------------------------------------
rolearnlist = []
rolearnlist_from_ssm = ssm_client.get_parameter(Name='rolearnlist')
rolearnlist_from_ssm_list = rolearnlist_from_ssm['Parameter']['Value'].split(",")
rolearnlist = rolearnlist_from_ssm_list
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# Loop through the list of ARNs, asynchronously invoke invoke_slave lambda and pass ARN
# -----------------------------------------------------------------------
for rolearn in rolearnlist:
x = {"ARN": rolearn}
invoke_response = client.invoke(FunctionName="invoke_slave",
InvocationType='Event', #assync exec
#InvocationType='RequestResponse', #sync exec
Payload=json.dumps(x))
print(invoke_response)
# -----------------------------------------------------------------------
这个Lambda主要是从SSM获取ARN参数值,该值中包含所有目标AWS账户中的IAM Role的ARN。
然后,轮询这个参数中的ARN,并把它作为参数调用 invoke_slave
Lambda函数。
- 创建Lambda: invoke_slave
用下面的代码来创建Lambda函数,并把
lambda_basicexec_crossaccount
IAM Role赋予该Lambda。
invoke_slave
Lambda的代码:
import boto3
import datetime
import time
stsclient = boto3.client('sts')
s3client = boto3.resource('s3')
def lambda_handler(event, context):
# -----------------------------------------------------------------------
# initiating a session using ARN of the IAM role
# -----------------------------------------------------------------------
rolearn = event['ARN']
awsaccount = stsclient.assume_role(
RoleArn=rolearn,
RoleSessionName='awsaccount_session'
)
ACCESS_KEY = awsaccount['Credentials']['AccessKeyId']
SECRET_KEY = awsaccount['Credentials']['SecretAccessKey']
SESSION_TOKEN = awsaccount['Credentials']['SessionToken']
# -----------------------------------------------------------------------
# create a list of all currently available aws regions
# -----------------------------------------------------------------------
ec2 = boto3.client('ec2', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY, aws_session_token=SESSION_TOKEN)
final_awsregionslist = []
awsregions = ec2.describe_regions()
awsregions_list = awsregions['Regions']
for region in awsregions_list:
final_awsregionslist.append(region['RegionName'])
# -----------------------------------------------------------------------
start = '::'
end = ':'
awsaccountid = rolearn[rolearn.find(start)+len(start):rolearn.rfind(end)] # getting awsaccount ID from IAM Role ARN
# -----------------------------------------------------------------------
# Building HTML page/table using jquery datatables
# -----------------------------------------------------------------------
date_now = datetime.date.today()
time_now = time.strftime("%H:%M:%S")
creationdatetime = f'last update: {date_now} {time_now} UTC'
payload_start = """<html><head><script src="https://code.jquery.com/jquery-3.3.1.min.js"></script><link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.css"><script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.js"></script><script>$(document).ready( function () {$('#example').DataTable();} );</script></head><body><table id="example" class="display"><thead><tr><th><font face="arial">AWS Account Id</font></th><th><font face="arial">AWS Region</font></th><th><font face="arial">EIP</font></th></tr></thead><tbody>"""
# -----------------------------------------------------------------------
# loop through all exisiting aws regions
# -----------------------------------------------------------------------
for awsregion in final_awsregionslist:
# =================== THIS IS WHERE YOUR JOB STARTS ==================
# ----------------------------------------------------------------------
# Open ec2 session for current aws account (arn) and region
ec2client = boto3.client('ec2', aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY, aws_session_token=SESSION_TOKEN, region_name=awsregion)
# ----------------------------------------------------------------------
response = ec2client.describe_addresses()
elasticipslist = response['Addresses']
for eip in elasticipslist:
if 'AssociationId' not in eip:
elastic_ip = eip['PublicIp']
# --------------------------------------------------------------
# Building HTML page/table
loopstring = f'<tr><td><font face="arial">{awsaccountid}</font></td><td><font face="arial">{awsregion}</font></td><td><font face="arial">{elastic_ip}</font></td></tr>'
payload_start = payload_start + loopstring
payload_end = f'</tbody></table><p>{creationdatetime}</p></body></html>'
finalpayload = payload_start + payload_end
# --------------------------------------------------------------
domain = 'your_s3_bucket_name_here' # S3 bucket name where HTML page will be saved (must be changed)
htmlfilename = f'awsaccount-{awsaccountid}-EIPs.html' # making unique name for HTML file
s3client.Object(domain, htmlfilename).put(Body=finalpayload, ContentType='text/html')
# =================== THIS IS WHERE YOUR JOB ENDS ===================
该lambda的主要逻辑:
- 通过传进来的目标IAM Role的ARN进行AssmeRole操作,从而获得临时的AccessKey和Token凭证。
- 之后使用上述获得的凭证进行获取当前AWS账户下的所有Region下的未使用的EIP。
- 将未使用的EIP作成HTML报表写入到S3 Bucket的文件中。
6. 测试
点击invoke_master
的Test进行测试,S3中会出现各个账户的html报表:
点击html,可以看到其中各个EIP信息: