低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

基础设施即代码:Terraform 和 AWS 无服务器

2019 年 1 月 22 日

基础设施即代码:Terraform和AWS无服务器

基础设施即代码

基础设施即代码(IaC)是一种通过机器可读的定义文件管理设备和服务器的方法。从根本上说,你要写下你希望基础设施是什么样子,以及应该在该基础设施上运行什么代码。然后,按下一个按钮,说“部署我的基础设施”即可。BAM 是你已经准备投入使用的应用程序,它运行在服务器上,后台是一个通过 API 访问的数据库。你刚刚用 IaC 定义了所有的基础设施。


IaC 是 DEVOPS 团队的一项重要实践,已经集成到 CI/CD 管道。


Terraform 是 HashiCorp 推出的一个很棒的基础设施即代码工具(https://www.terraform.io/)。


我个人使用它来提供和维护 AWS 上的基础设施。我在这方面有很好的经验。



介绍和演示

我将通过一个例子来演示 IaC。我们将在 AWS 上创建一个应用程序。我在 GitLab 上提供了代码:https://gitlab.com/nxtra/codingtips-blog。用户可以输入编码提示并查看其他用户输入的所有编码提示。这些提示存储在一个 NoSQL 数据库 AWS DynamoDB 中。存储和检索这些提示是由 Lambda 函数完成的,这些函数从数据库中获取或存放提示。要使用这个应用程序,用户必须能够调用这些 Lambda 函数。因此,我们通过 AWS API 网关公开 Lambda 函数。以下是该应用程序的总体架构。



你可以将这些函数组合到一个 Web 页面上,用户可以在该页面中输入提示并查看已有的所有提示。下面是最终结果:



让我们深入了解一下。


创建应用程序

现在,我将介绍创建你在上面的演示中所看到的应用程序的步骤。IaC 是重点。我将展示必要的代码和 AWS CLI 命令,但我不会详细解释它们,因为这不是本文的目的。我将重点介绍 Terraform 的定义。欢迎复制我在下文中提供的存储库,并按照本文提供的步骤进行操作。


准备工作


Terraform:基础

你将使用 Terraform 配置的主要内容是资源。资源是应用程序基础设施的组件。例如,一个 Lambda 函数、一个 API 网关部署、一个 DynamoDB 数据库……资源是通过在关键字 resource 后面加上类型和名称来定义的。名称可以任意选择。类型是固定的。例如:resource “aws_dynamodb_table” “codingtips-dynamodb-table”。


要按照这篇文章的介绍进行操作,你需要知道以下两个基本的 Terraform 命令。


terraform apply


Terraform apply 将开始准备你定义的所有基础设施,创建你的数据库,设置 Lambda 函数,准备 API 网关。


terraform destroy


Terraform destroy 将删除你在云中配置的所有基础设施。如果你正确地使用了 Terraform,应该就用不到这个命令。但是,如果你想从头重新开始,则可以使用此命令删除所有现有的基础设施。不用担心,你的机器上仍然有所有基础设施的完整描述,因为你使用的是基础设施即代码。


我们将把所有使用 Terraform 定义的基础设施放在同一个文件夹中。文件需要使用.tf 扩展名。


General

让我们从创建一个 general.tf 文件开始。


provider "aws" {  region = "eu-west-1"}
# 变量variable "lambda_version" { default = "1.0.0"}variable "s3_bucket" { default = "codingtips-node-bucket"}
复制代码


Provider 块指明我们正在向 AWS 上部署。你还可以在这里提供将用于部署的凭证。如果你已经在自己的机器上正确地设置了 AWS CLI,那么,.aws 文件夹中就会有默认凭证。如果没有指定凭证,Terraform 将使用这些默认凭证。


变量有一个名称,我们可以在 Terraform 配置的任何地方引用它。例如,我们可以使用 ${var.s3_bucket)引用 s3_bucket 变量。当你在多个地方使用相同的变量时,这非常方便。在这篇文章中,我不会使用太多的变量,因为这会使你的 Terraform 配置添加更多的引用,我希望它尽可能清晰。


数据库:DynamoDB

我们从基础开始。我们所有的编码提示将存储在哪里?没错,在数据库中。这个数据库是我们的基础设施的一部分,将在一个名为 dynamo.tf 的文件中定义。


resource "aws_dynamodb_table" "codingtips-dynamodb-table" {  name = "CodingTips"  read_capacity = 5  write_capacity = 5  hash_key = "Author"  range_key = "Date"
attribute = [ { name = "Author" type = "S" }, { name = "Date" type = "N" }]}
复制代码


因为 Dynamo 是一个 NoSQL 数据库,我们不需要预先指定所有属性。我们唯一需要提供的是 AWS 用来构建分区键的元素。当你提供一个哈希键和一个排序键时,AWS 会把它们组合成一个惟一的分区键。注意“UNIQUE”这个词。确保这个组合是唯一的。


DynamoDB 使用分区键值作为内部哈希函数的输入。哈希函数的输出决定了项存储在哪个分区(DynamoDB 的内部物理存储)。具有相同分区键值的所有项存储在一起,按键值排序。——来自 AWS 文档:DynamoDB核心组件


从 dynamo.tf 中的属性定义可以很容易地看出,Author (S)是一个字符串,Date (N)应该是一个数字。


IAM

在指定 Lambda 函数之前,我们必须为要使用的函数创建权限。这确保我们的函数具有访问其他资源(如 DynamoDB)的权限。简单来说,AWS 权限模型的工作原理如下:


  • 提供一个带角色的资源

  • 给该角色添加权限

  • 以下权限允许该角色访问其他资源:

  • 触发另一个资源(如 Lambda 函数将日志转发到 CloudWatch)的权限

  • 被另一个资源(如 Lambda 函数被 API 网关触发)触发的权限


# ROLES# IAM角色规定Lambda函数可以访问的其他AWS服务resource "aws_iam_role" "lambda-iam-role" {  name = "codingtips_lambda_role"
assume_role_policy = <<EOF{ "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "lambda.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ]}EOF}
# POLICIESresource "aws_iam_role_policy" "dynamodb-lambda-policy"{ name = "dynamodb_lambda_policy" role = "${aws_iam_role.lambda-iam-role.id}" policy = <<EOF{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "dynamodb:*" ], "Resource": "${aws_dynamodb_table.codingtips-dynamodb-table.arn}" } ]}EOF}
复制代码


在上面的例子中,定义的第一个资源是 aws_iam_role。我们稍后会把这个角色赋予 Lambda 函数。


然后,我们创建了资源 aws_iam_role_policy,将其关联到到角色 aws_iam_role。第一个 aws_iam_role_policy 允许这个角色调用指定 DynamoDB 资源上的任何操作。第二个 role_policy 允许具有此角色的资源向 CloudWatch 发送日志。


以下是几个注意事项:


  • aws_iam_role 和 aws_iam_role_policy 通过 role_policy 资源的 role 参数关联;

  • 在 aws_iam_role_policy 的 Statement 属性里,我们赋予(Effect 属性)在特定资源上(Resource 属性)执行某些操作的权限(Action 属性);

  • 资源通过 ARN Amazon Resource Name 引用,这是资源在 AWS 上的唯一标识;

  • 有两种方式可以指定 aws_iam_role_policy:

  • 使用 until EOF 语法(正如我此处的做法)

  • 使用一个单独的 Terraform aws_iam_policy_document 元素,然后耦合到 aws_iam_role_policy

  • dynamodb-lambda-policy 允许在指定的 DynamoDB 资源上执行所有操作,因为 Action 属性的设置为 dynamodb:* 。你可以像下面这样进行更严格的限制,并指出可以执行的操作:


"dynamodb:Scan", "dynamodb:BatchWriteItem","dynamodb:PutItem"
复制代码


Lambda 函数

这个应用程序有两个 Lambda 函数。第一个 Lambda 用于从数据库中获取或检索编码提示,引用名是 getLambda。第二个 Lambda 用于将编码提示发布或发送到数据库,引用名是 postlambda。


我不打算在这里复制粘贴 Lambda 函数的代码。你可以通过本文提供的存储库链接查看(GitLab 存储库:https://gitlab.com/nxtra/codingtips-blog)


这里,我将以 getLambda 函数为例。postLambda 以相同的方式部署,你可以在 Git 存储库中找到 Terraform 定义。Lambda 函数与我们在这里定义的其他基础设施稍有不同。我们不仅需要一个 Lambda 函数作为基础设施。我们还需要指定在 Lambda 函数中运行的代码。但是,在部署 Lambda 函数时,AWS 将在哪里找到特定的代码呢?他们无法访问你的本地机器,是吗?这就是为什么你需要首先将代码发送到 AWS 上的 S3 桶,以便在部署函数时可以在那里找到代码。


这就意味着要创建一个 S3 桶,当你想在 eu-west-1(爱尔兰)地区创建时,可以使用下面的命令:


aws s3api create-bucket --bucket codingtips-node-bucket --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-1
复制代码


现在,你需要使用 zip 压缩你的 Lambda 函数代码:


zip -r getLambda.zip index.js
复制代码


并把那个文件上传到 S3:


aws s3 cp getLambda.zip s3://codingtips-node-bucket/v1.0.0/getLambda.zip
复制代码


注意,我将把它发送到存储桶 codingtips-node-bucket 的文件夹 v1.0.0 下,文件名为 getLambda.zip。


好了,代码已经到了该到的地方。现在让我们看看如何使用 Terraform 来指定这些函数。


resource "aws_lambda_function" "get-tips-lambda" {  function_name = "codingTips-get"
# 桶名和之前创建的一样,为"aws s3api create-bucket" s3_bucket = "${var.s3_bucket}" s3_key = "v${var.lambda_version}/getLambda.zip"
# "main"是zip文件中的文件名(index.js),"handler"是属性名,其值是那个文件 # 输出的handler函数 handler = "index.handler" runtime = "nodejs8.10" memory_size = 128
role = "${aws_iam_role.lambda-iam-role.arn}"}
resource "aws_lambda_permission" "api-gateway-invoke-get-lambda" { statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" function_name = "${aws_lambda_function.get-tips-lambda.arn}" principal = "apigateway.amazonaws.com"
# /*/*部分允许从指定API网关中的任何资源上的任何方法访问 source_arn = "${aws_api_gateway_deployment.codingtips-api-gateway-deployment.execution_arn}/*/*"}
复制代码


  • 注意,我们把 S3 桶和查找代码的目录告知 Terraform;

  • 我们为这个 Lambda 函数指定运行时和内存;

  • index.handler 指向该文件和进入代码的函数;

  • aws_lambda_permission 资源是权限,说明该 Lambda 函数可以被我们创建的 API 网关调用。


API 网关

我把最难的内容留到最后一部分介绍。另一方面,它也是最有趣的。我给 Terraform 提供 API 的 Swagger 定义。你也可以不使用 Swagger,但是,你必须指定更多的资源。


Swagger API 定义如下:


swagger: '2.0'info:  version: '1.0'  title: "CodingTips"schemes:  - httpspaths:  "/api":    get:      description: "Get coding tips"      produces:        - application/json      responses:        200:          description: "The codingtips request successful."          schema:            type: array            items:              $ref: "#/definitions/CodingTip"      x-amazon-apigateway-integration:        uri: ${get_lambda_arn}        passthroughBehavior: "when_no_match"        httpMethod: "POST"        type: "aws_proxy"    post:      description: "post a coding tip"      consumes:        - application/json      responses:        200:          description: "The codingtip was added successfully"      x-amazon-apigateway-integration:        uri: ${post_lambda_arn}        passthroughBehavior: "when_no_match"        httpMethod: "POST"        type: "aws_proxy"
definitions: CodingTip: type: object description: "A coding tip" properties: tip: type: string description: "The coding tip" date: type: number description: "date in millis when tip was entered" author: type: string description: "Author of the coding tip" category: type: string description: "category of the coding tip" required: - tip
复制代码


如果你还不知道 Swagger,复制上述代码并粘贴到在线编辑器里(Swagger编辑器)。


这将使你对 API 定义有一个直观的了解。



在上述 Swagger 规范中只有一个是 AWS 特定的东西,那就是 x-amazon-apigateway-integration。这指定了 API 与后端集成的细节。


  • 注意,这里永远是 POST,即使资源路径的 HTTP 方式是 GET;

  • aws_proxy 表示不对请求做任何操作就传递给 Lambda 函数;

  • 当没有为 Content-Type 指定 requestTemplate 时,when_no_match 会将请求体直接传到后台,而不做转换;

  • uri 引用一个变量,如 ${get_lambda_arn},Terraform 会将其传递给 Swagger 定义。我们一会就会看到。


前面已经提到,使用 Swagger 定义 API 网关有一些优点:


  • 使 Terraform 更为简明;

  • 你可以通过 Swagger 获得 API 的良好表示。


resource "aws_api_gateway_rest_api" "codingtips-api-gateway" {  name        = "CodingTipsAPI"  description = "API to access codingtips application"  body        = "${data.template_file.codingtips_api_swagger.rendered}"}
data "template_file" codingtips_api_swagger{ template = "${file("swagger.yaml")}"
vars { get_lambda_arn = "${aws_lambda_function.get-tips-lambda.invoke_arn}" post_lambda_arn = "${aws_lambda_function.post-tips-lambda.invoke_arn}" }}
resource "aws_api_gateway_deployment" "codingtips-api-gateway-deployment" { rest_api_id = "${aws_api_gateway_rest_api.codingtips-api-gateway.id}" stage_name = "default"}
output "url" { value = "${aws_api_gateway_deployment.codingtips-api-gateway-deployment.invoke_url}/api"}
复制代码


  • 我们首先列出了资源 aws_api_gateway_rest_api resource。该资源就像它的名称一样,提供一个 API 网关 REST API:

  • body 引用 Swagger 文件;

  • template_file 数据源允许 Terraform 使用 Terraform(在这里是 Swagger )中未定义的信息:

  • 传递到 template_file 的变量用于填充文件;

  • 要使指定的 REST API 可用,它必须:

  • 通过资源 aws_api_gateway_deployment 部署;

  • 引用该 REST API;

  • 它需要一个 stage,这好比 API 的“版本”或“快照”,stage_name 将用在 URL 中,用于调用这个 API;

  • 最后,可以用来调用这个 API 的 URL 被输出到终端,并在末尾加上/api 以提供正确的资源路径。


尾声

好了,我们现在来看看,这是否真得可行。这里,我在和这篇博文相关的存储库中运行 terraform apply。



不错,它可以工作。我只告诉 Terraform 我想要的基础设施。整个安装过程自动进行!现在,你可以使用输出的 URL 来 GET 和 POST 编码提示。POST 体应该是这样的:


{  "author": "Nick",  "tip": "Short sessions with frequent brakes",  "category": "Empowerment"}
复制代码


当你需要将 API 端点耦合到自己设计的前端时,你需要正确设置 CORS 头。如果你想迎接这个挑战,在我解决这个问题的存储库中(cors-enabled)还有另一个分支。


快乐编码人,用代码写下基础设施!


查看英文原文:INFRASTRUCTURE AS CODE: TERRAFORM AND AWS SERVERLESS


2019 年 1 月 22 日 15:496523
用户头像

发布了 1008 篇内容, 共 315.1 次阅读, 收获喜欢 284 次。

关注

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布
暂无评论
发现更多内容

将自媒体玩得风生水起的不一定是前总统,还有可能是艺术家

zhoo299

艺术 自媒体 五月日更

浅入Spring Cloud架构

Damon

微服务 微服务框架 微服务切分 5月日更 五月日更

【LeetCode】子数组异或查询Java题解

HQ数字卡

算法 LeetCode 5月日更

「技术人生」专题第1篇:什么是技术一号位?

阿里巴巴中间件

华为云PB级数据库GaussDB(for Redis)揭秘第十期:GaussDB(for Redis)迁移系列(上)

华为云开发者社区

数据仓库 华为云 数据迁移 GaussDB(for Redis) PB级数据库

再学习一个 Golang 专栏

escray

go 极客时间 学习笔记 五月日更 Go语言核心36讲

传统BI如何转大数据数仓

数据社

大数据 BI 5月日更

【图片旋转】十五分钟挑战鸿蒙Codelab组件

liuzhen007

鸿蒙 5月日更

Gradle学习笔记

ES_her0

5月日更

学习笔记:02 | 第一个程序:教你输出彩色的文字

Nydia

学习笔记

全靠这份《Java 10W 字+面试总结》,让我在“银四”收割了5个大厂offer

神奇小汤圆

Java 程序员 架构 面试

你认识镜子里的那个自己吗?

小天同学

原则 认知 5月日更

流量变现业务概论——Linkedmall流量变现业务初步分析及系统设计概要

关贺宇

关于组件,你真的了解么?

架构精进之路

组件化 5月日更

零基础学习NLP-DAY2

April (Sophie)

nlp 5月日更 五月日更

源码解析之Seata项目中的分布式ID生成算法

Coder的技术之路

分布式 分布式ID

Spring Cloud Alibaba 生态学习

风翱

spring cloud alibaba 5月日更

人证一体机产品设计

lenka

5月日更

NumPy之:理解广播

程序那些事

Python Numpy 程序那些事

「Adobe国际认证」了解Adobe Photoshop,图像大小和分辨率教程

Adobe国际认证

直呼内行!阿里珍藏版:《阿里巴巴面试参考指南(泰山版)》,面面俱到太全了

神奇小汤圆

Java 程序员 架构 面试

技术笔记(语音识别-语音噪声分类)

攻城先森

噪声 5月日更 技术笔记

网络攻防学习笔记 Day11

穿过生命散发芬芳

5月日更 网络攻防

前端开发:Promise的使用

三掌柜

5月日更 5月

这个云原生开发的痛点你遇到了吗?

阿里巴巴中间件

防治肥胖,AI转向

脑极体

网络协议之HTTP:HTTP 1.1与HTTP 2

程序员架构进阶

HTTP2.0 28天写作 HTTP协议 5月日更

Ansible AD-Hoc

耳东

ansible 5月日更

Windows自带的功能这么好用,还装什么第三方软件?

彭宏豪95

windows 5月日更

编程思考路径2条

顿晓

5月日更 思考路径

由高频护网设备漏洞引发的供应链浅思

马士兵-苹果老师

安全

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

基础设施即代码:Terraform和AWS无服务器-InfoQ