构建安全且经济高效的 AWS Lambda 应用
1. 加密环境变量
在 AWS Lambda 中,环境变量可以在不更改代码的情况下,动态地将数据传递给函数代码。根据十二要素应用方法,应将配置与代码分离,以避免将敏感凭证提交到代码仓库,并能使用相同的源代码定义 Lambda 函数的多个版本(如开发、生产和沙盒环境)。此外,环境变量还可用于基于不同设置更改函数行为,例如 A/B 测试。如果需要在多个 Lambda 函数之间共享机密信息,可以使用 AWS 的系统管理器参数存储。
以下是一个使用环境变量传递 MySQL 凭证到函数代码的示例:
func handler() error {
MYSQL_USERNAME := os.Getenv("MYSQL_USERNAME")
MYSQL_PASSWORD := os.Getenv("MYSQL_PASSWORD")
MYSQL_DATABASE := os.Getenv("MYSQL_DATABASE")
MYSQL_PORT := os.Getenv("MYSQL_PORT")
MYSQL_HOST := os.Getenv("MYSQL_HOST")
uri := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", MYSQL_USERNAME, MYSQL_PASSWORD, MYSQL_HOST,
MYSQL_PORT, MYSQL_DATABASE)
db, err := sql.Open("mysql", uri)
if err != nil {
return err
}
defer db.Close()
_, err = db.Query(`CREATE TABLE IF NOT EXISTS movies(id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL)`)
if err != nil {
return err
}
for _, movie := range []string{"Iron Man", "Thor", "Avengers", "Wonder Woman"} {
_, err := db.Query("INSERT INTO movies(name) VALUES(?)", movie)
if err != nil {
return err
}
}
movies, err := db.Query("SELECT id, name FROM movies")
if err != nil {
return err
}
for movies.Next() {
var name string
var id int
err = movies.Scan(&id, &name)
if err != nil {
return err
}
log.Printf("ID=%d\tName=%s\n", id, name)
}
return nil
}
将函数部署到 AWS Lambda 并设置环境变量后,即可调用该函数,它将输出插入到数据库中的电影列表。但需要注意的是,数据库凭证是以明文形式存在的。幸运的是,AWS Lambda 使用 AWS 密钥管理服务(KMS)提供了传输中和静止时的两级加密。
1.1 静止数据加密
AWS Lambda 在部署函数时会对所有环境变量进行加密,并在函数调用时(即时)进行解密。默认情况下,AWS Lambda 使用默认的 Lambda 服务密钥对静止的环境变量进行加密,该密钥会在首次在特定区域创建 Lambda 函数时自动创建。
若要更改密钥并使用自己的密钥,可按以下步骤操作:
1. 导航到身份和访问管理控制台。
2. 点击“加密密钥”。
3. 点击“创建密钥”按钮创建新的客户主密钥(CMK)。
4. 选择一个 IAM 角色和账户,通过密钥管理服务(KMS)API 来管理该密钥,并选择创建 Lambda 函数时使用的 IAM 角色,以便 Lambda 函数能够使用该 CMK 并成功请求加密和解密方法。
5. 密钥创建完成后,返回 Lambda 函数配置页面,将密钥更改为刚创建的密钥。
此时,AWS Lambda 在将环境变量存储在 Amazon 时,将使用你自己的密钥对其进行静止加密。
1.2 传输数据加密
建议在部署函数之前对环境变量(敏感信息)进行加密。AWS Lambda 在控制台提供了加密助手,使此过程易于操作。
若要进行传输加密(使用前面提到的 KMS),可按以下步骤操作:
1. 勾选“启用传输加密助手”复选框。
2. 点击相应的“加密”按钮,对
MYSQL_USERNAME
和
MYSQL_PASSWORD
进行加密。加密后的凭证将以密文形式显示在控制台中。
3. 更新函数的处理程序,使用 KMS SDK 对环境变量进行解密:
var encryptedMysqlUsername string = os.Getenv("MYSQL_USERNAME")
var encryptedMysqlPassword string = os.Getenv("MYSQL_PASSWORD")
var mysqlDatabase string = os.Getenv("MYSQL_DATABASE")
var mysqlPort string = os.Getenv("MYSQL_PORT")
var mysqlHost string = os.Getenv("MYSQL_HOST")
var decryptedMysqlUsername, decryptedMysqlPassword string
func decrypt(encrypted string) (string, error) {
kmsClient := kms.New(session.New())
decodedBytes, err := base64.StdEncoding.DecodeString(encrypted)
if err != nil {
return "", err
}
input := &kms.DecryptInput{
CiphertextBlob: decodedBytes,
}
response, err := kmsClient.Decrypt(input)
if err != nil {
return "", err
}
return string(response.Plaintext[:]), nil
}
func init() {
decryptedMysqlUsername, _ = decrypt(encryptedMysqlUsername)
decryptedMysqlPassword, _ = decrypt(encryptedMysqlPassword)
}
func handler() error {
uri := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", decryptedMysqlUsername, decryptedMysqlPassword,
mysqlHost, mysqlPort, mysqlDatabase)
db, err := sql.Open("mysql", uri)
if err != nil {
return err
}
...
}
若使用了自己的 KMS 密钥,需要为附加到 Lambda 函数的执行角色(IAM 角色)授予
kms:Decrypt
权限,并适当增加默认执行超时时间,以确保函数代码有足够的时间完成执行。
2. 使用 CloudTrail 记录 AWS Lambda API 调用
捕获 Lambda 函数进行的所有调用对于审计、安全和合规性至关重要,它能让你全面了解 Lambda 函数与哪些 AWS 服务进行了交互。CloudTrail 就是利用这一特性的服务之一。
CloudTrail 会记录 Lambda 函数进行的 API 调用,使用起来简单直接。具体操作如下:
1. 从 AWS 管理控制台导航到 CloudTrail。
2. 按事件源过滤事件,事件源应为
lambda.amazonaws.com
。
在那里,你将看到每个 Lambda 函数进行的所有调用。除了展示事件历史记录,还可以在每个 AWS 区域创建一个跟踪,将 Lambda 函数的事件记录到单个 S3 存储桶中,然后使用 ELK(Elasticsearch、Logstash 和 Kibana)堆栈实现日志分析管道来处理日志。最后,还可以在 Kibana 中创建交互式和动态小部件,构建仪表板以查看 Lambda 函数事件。
3. 依赖项的漏洞扫描
由于大多数 Lambda 函数代码包含多个第三方 Go 依赖项,因此对这些依赖项进行审计非常重要。对 Golang 依赖项进行漏洞扫描应成为 CI/CD 的一部分,必须使用第三方工具(如 Snyk)自动进行安全分析,以持续扫描依赖项中已知的安全漏洞。
通过将漏洞扫描纳入工作流程,可以发现并修复包中可能导致数据丢失、服务中断和未经授权访问敏感信息的已知漏洞。此外,应用程序的最佳实践在无服务器架构中仍然适用,例如代码审查、使用 Git 分支以及进行输入验证或清理以避免 SQL 注入等安全检查。
4. AWS Lambda 定价模型
AWS Lambda 改变了运维团队配置和管理组织基础设施的方式,客户现在可以运行代码而无需担心底层基础设施,并且成本较低。每月前 100 万个请求是免费的,之后每 100 万个请求收费 0.20 美元,因此你可以无限期使用 Lambda 的免费层。但如果使用强度较大或处理大量工作负载的应用程序,若不特别关注函数的资源使用和代码优化,可能会产生不必要的高额费用。
Lambda 函数的成本由以下三个因素决定:
| 因素 | 说明 |
| ---- | ---- |
| 执行次数 | 调用次数,每次请求收费 0.0000002 美元。 |
| 分配内存 | 为函数分配的 RAM 量,范围在 128 MB 到 3,008 MB 之间。 |
| 执行时间 | 从代码开始执行到返回响应或终止的持续时间,时间将四舍五入到最接近的 100 毫秒(Lambda 按 100 毫秒增量计费),最大超时时间可设置为 5 分钟。 |
| 数据传输 | 如果 Lambda 函数发起外部数据传输,将按 EC2 数据传输费率收费。 |
4.1 Lambda 成本计算示例
假设为
FindAllMovies
函数分配了 128 MB 的内存,并将执行超时时间设置为 3 秒,该函数每秒执行 10 次(每月 2500 万次),则费用计算如下:
-
每月计算费用
:每月计算价格为每 GB/秒 0.00001667 美元,免费层提供 400,000 GB/秒。
- 总计算时间(秒) = 2500 万 * 1 秒 = 25,000,000 秒。
- 总计算量(GB/秒) = 25,000,000 * 128 MB / 1,024 = 3,125,000 GB/秒。
- 每月应计费计算量(GB/秒) = 总计算量 - 免费层计算量 = 3,125,000 GB/秒 - 400,000 免费层 GB/秒 = 2,725,000 GB/秒。
- 每月计算费用 = 2,725,000 GB/秒 * 0.00001667 美元 = 45.42 美元。
-
每月请求费用
:每月请求价格为每 100 万个请求 0.20 美元,免费层每月提供 100 万个请求。
- 每月应计费请求数 = 总请求数 - 免费层请求数 = 2500 万请求 - 100 万免费层请求 = 2400 万每月应计费请求。
- 每月请求费用 = 2400 万 * 0.2 美元/百万 = 4.8 美元。
因此,每月总费用 = 计算费用 + 请求费用 = 45.42 美元 + 4.8 美元 = 50.04 美元。
5. 最佳内存大小选择
如前文所述,分配的 RAM 量会影响计费,同时也会影响函数获得的 CPU 和网络带宽。因此,需要选择最佳的内存大小。为了找到函数价格和性能的最佳平衡点,必须使用不同的内存设置测试 Lambda 函数,并分析函数实际使用的内存。
AWS Lambda 会在关联的日志组中写入日志条目,其中包含每个请求分配和使用的内存量。通过比较“内存大小”和“最大使用内存”字段,可以确定函数是否需要更多内存,或者是否过度配置了函数的内存大小。如果函数需要更多内存,可以在“基本设置”部分增加内存,点击“保存”后再次调用函数,会发现内存大小会影响执行时间。
增加函数的内存设置可以显著提高性能,但成本也会随着内存设置的增加而线性增加。同样,减少函数的内存设置可能有助于降低成本,但会增加执行时间,在最坏的情况下,可能会导致超时或内存不足错误。为 Lambda 函数配置最小的内存设置并不总是能提供最低的总成本,因为函数可能会因内存不足而失败或超时,执行时间也可能会更长,从而导致支付更多费用。
6. 代码优化
在优化资源使用之前,应先优化函数代码,以减少函数执行所需的内存和 CPU 资源。与传统应用程序不同,AWS Lambda 会为你管理和修补基础设施,使开发人员能够专注于编写高质量、高效且执行速度快的代码。
在设计具有成本效益的 AWS Lambda 函数时,可参考以下几点:
-
利用热容器
:对于某些请求,可以使用热容器。可以通过以下方式提高 Lambda 函数的性能:
- 使用全局变量和单例模式,避免每次调用时重新初始化变量。
- 保持并重用之前调用期间建立的数据库和 HTTP 连接。在 Go 语言中,可以使用
init
函数设置所需的状态,并在加载函数处理程序时运行一次性计算。
-
设计异步架构
:解耦的组件完成工作所需的计算时间可能比紧密耦合的组件少,同时避免在等待同步请求响应时浪费 CPU 周期。
-
使用监控和调试工具
:如 AWS X-Ray,用于分析和排查影响 Lambda 应用程序性能的瓶颈、延迟峰值和其他问题。
-
设置限制
:使用并发预留设置限制,防止无限制的自动扩展和冷启动,并保护下游服务。还可以在 Lambda 触发器和函数之间放置简单队列服务(SQS),调整 Lambda 函数的触发频率,从而限制执行次数。
7. Lambda 成本和内存跟踪
设计具有成本效益的 AWS Lambda 无服务器应用程序的关键在于监控成本和资源使用情况。遗憾的是,CloudWatch 并未直接提供有关资源使用或 Lambda 函数成本的指标。不过,每次执行时,Lambda 函数会将执行日志写入 CloudWatch,示例如下:
REPORT RequestId: 147e72f8-5143-11e8-bba3-b5140c3dea53 Duration: 12.00 ms Billed Duration:
100 ms Memory Size: 128 MB Max Memory Used: 21 MB
该日志显示了特定请求分配和使用的内存量,可以使用简单的 CloudWatch 日志指标过滤器提取这些值。具体操作步骤如下:
1. 打开 AWS CloudWatch 控制台,从导航窗格中选择“日志组”。
2. 搜索与 Lambda 函数关联的日志组,其名称应为
/aws/lambda/FUNCTION_NAME
。
3. 点击“创建指标过滤器”按钮。
4. 定义一个解析以空格分隔的术语的指标过滤器模式,该模式需指定字段名称,用逗号分隔,整个模式用方括号括起来,例如
[a,b,c]
。点击“测试模式”,测试过滤器模式对日志中现有数据的过滤结果。
5. 如果不知道字段数量,可以使用方括号括起来的省略号。
6. 第 13 列将存储分配给函数的内存,第 18 列表示实际使用的内存。点击“分配指标”,为分配的内存创建一个指标,然后点击“创建过滤器”保存。
7. 重复上述步骤,为内存使用情况创建另一个过滤器。
8. 定义好两个过滤器后,确保 Lambda 函数正在运行,等待几秒钟,让函数为新的 CloudWatch 指标填充一些值。
9. 返回 CloudWatch,基于之前创建的两个指标创建一个新图表。
还可以进一步创建近乎实时的 CloudWatch 警报,当使用的内存超过一定阈值(例如分配内存的 80%)时触发。此外,密切关注函数的执行时间也很重要,可以按照上述步骤从 Lambda 执行日志中提取计费持续时间,并根据提取的值设置警报,以便在函数执行时间过长时收到通知。
综上所述,使用 AWS Lambda 入门很容易,无需配置和管理底层基础设施,且能在短时间内以低成本让应用程序运行起来。与 EC2 相比,AWS Lambda 的一大优势是无需为闲置资源付费,但这也是 Lambda 的一大风险。在开发过程中很容易忽略成本问题,但在生产环境中运行大量工作负载和多个函数时,成本可能会变得很高。因此,在问题出现之前,密切跟踪 Lambda 的成本和使用情况非常重要。
8. 总结与展望
在构建基于 AWS Lambda 的无服务器应用时,安全性和成本效益是两个至关重要的方面。通过前面的介绍,我们了解了一系列保障安全和优化成本的方法。
8.1 安全保障总结
- 加密环境变量 :利用 AWS KMS 对环境变量进行传输中和静止时的加密,保护敏感信息不被泄露。在静止数据加密中,可使用默认的 Lambda 服务密钥或自定义的客户主密钥;在传输数据加密时,借助 AWS Lambda 控制台的加密助手,结合 KMS SDK 实现加密和解密操作。
- 日志记录与分析 :使用 CloudTrail 记录 Lambda 函数的 API 调用,有助于审计、安全和合规性检查。还可结合 ELK 堆栈进行日志分析,在 Kibana 中创建仪表板查看函数事件。
- 依赖项漏洞扫描 :将对 Golang 依赖项的漏洞扫描纳入 CI/CD 流程,使用第三方工具(如 Snyk)持续检测依赖项中的安全漏洞,避免潜在的安全风险。
8.2 成本优化总结
- 定价模型理解 :明确 Lambda 函数成本由执行次数、分配内存、执行时间和数据传输四个因素决定,通过合理规划这些因素来控制成本。
- 最佳内存选择 :通过测试不同内存设置,分析函数实际内存使用情况,找到价格和性能的最佳平衡点。避免过度配置或配置不足的内存,以免影响成本和性能。
- 代码优化 :采用利用热容器、设计异步架构、使用监控和调试工具、设置限制等方法优化代码,减少资源消耗,提高执行效率。
- 成本和内存跟踪 :通过 CloudWatch 日志指标过滤器提取 Lambda 函数的内存分配和使用情况,创建图表和警报,实时监控成本和资源使用,及时发现并解决问题。
8.3 未来展望
接下来,我们可以引入基础设施即代码(IaC)的概念,实现 N 层无服务器应用的自动化设计和部署。IaC 可以将基础设施的配置和部署过程以代码的形式进行管理,避免人为错误和重复劳动,提高部署的一致性和可靠性。以下是一个简单的 mermaid 流程图,展示了 IaC 在无服务器应用部署中的基本流程:
graph LR
A[编写 IaC 代码] --> B[代码审查]
B --> C[部署到测试环境]
C --> D{测试是否通过?}
D -- 是 --> E[部署到生产环境]
D -- 否 --> F[修改 IaC 代码]
F --> B
通过 IaC,我们可以将无服务器应用的部署过程标准化和自动化,进一步提高开发和运维效率,降低成本。同时,结合前面介绍的安全和成本优化方法,构建更加安全、高效、经济的无服务器应用。
总之,在使用 AWS Lambda 构建无服务器应用时,我们要始终关注安全和成本问题,不断优化和改进,以适应不断变化的业务需求和技术环境。通过合理运用各种工具和方法,我们可以充分发挥 AWS Lambda 的优势,实现无服务器应用的成功部署和运行。
超级会员免费看
366

被折叠的 条评论
为什么被折叠?



