Terraform、S3、Lambda、Travis 和 API Gateway

分享此内容
分享此内容

Terraform 是一种开源的基础设施即代码软件工具,使您能够安全、可预测地创建、更改和改进基础设施。 这是一篇比较长的博文,但 Terraform 正在变得越来越重要,它成为开发者技术栈中不可或缺的一部分,所以让我们从一些基础知识开始,然后慢慢过渡到 Terraform 和 Travis CI 更复杂的使用场景。 我们开始吧!

入门

让我们看一下我的 terraform_config 文件

# Montana's Terraform config

provider "aws" {
  region = "eu-west-1"
  #access_key = "PUT-YOUR-ACCESS-KEY-HERE"
  #secret_key = "PUT-YOUR-SECRET-KEY-HERE"
  version = "~> 2.55.0"
}

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
  required_version = ">= 0.13"
}

在配置文件中您会注意到我选择了 AWS 作为我的提供商,还有 ENVIRONMENT_VARIABLES、Terraform 的版本以及我想要强制执行的版本。

我们假设这个项目处于共享环境中,因此 local state 不够,我们需要使用 remote state。 简而言之,在 Terraform 中使用远程状态,Terraform 将状态数据写入远程数据存储,在本例中可能是云存储,然后可以与团队的所有成员共享。 可以把它想象成一个 Google 文档,您可以选择谁可以访问它。

在进入架构部分之前,请记住这个 Terraform 配置文件列表

credentials
credentials_helper
diable_checkpoint
disable_checkpoint_signature
plugin_cache_dir
provider_installation

架构

因此,您作为一名开发者决定使用 Travis CI,它是互联网上最好的构建工具,首先,这是一个不错的选择! 以下是一些您需要了解的要点,以便顺利开始

  • 将 Travis CI 连接到您的 GitHub 帐户,并选择您的存储库副本
  • 创建了一个具有 程序化访问权限 的 AWS IAM 用户

我们知道 Travis 的作用,但是如果您不知道,在本例中,Travis 会构建网站工件,部署基础设施,并将工件推送到生产环境而不是暂存环境。

在本例中,我将使用 Amazon S3 后端以及 DynamoDB 来存储 Terraform 状态。 我发现 DynamoDB 与 Teraform 配合使用是最友好的。 Terraform 将状态(现在记住,不是本地状态,而是远程状态)存储在 S3 中,并使用 DynamoDB 在执行更改时获取锁。 锁定很重要,可以避免两个 Terraform 二进制文件同时修改同一状态。 如果两个 Terraform 实例正在执行此操作,那么您就会看到由此引发的麻烦和头痛。

Terraform 配置文件

让我们从 S3 后端配置文件开始

terraform {
  required_version = ">= 0.12"
}

provider "aws" {}

data "aws_caller_identity" "current" {}

locals {
  account_id = data.aws_caller_identity.current.account_id
}

resource "aws_s3_bucket" "terraform_state" {
  bucket = "${local.account_id}-terraform-states"

  versioning {
    enabled = true
  }

  # I strongly encourage enabling server side encryption by default - Montana
  
  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
    }
  }
}

现在让我们创建 DynamoDB Terraform 配置文件

resource "aws_dynamodb_table" "terraform_lock" {
  name         = "terraform-lock"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

您会看到已启用 "lock" 方法以强制执行仅允许一个 Terraform 实例生成。 接下来,您需要在这个过程中提供详细的信息,因此让我们设置一些 Terraform 输出

output "s3_bucket_name" {
  value       = aws_s3_bucket.terraform_state.id
  description = "Montana's Terraform S3 Bucket"
}

output "s3_bucket_arn" {
  value       = aws_s3_bucket.terraform_state.arn
  description = "Montana's ARN Bucket"
}

output "s3_bucket_region" {
  value       = aws_s3_bucket.terraform_state.region
  description = "Montana's S3 Region of the Bucket"
}

output "dynamodb_table_name" {
  value       = aws_dynamodb_table.terraform_lock.name
  description = "Montana's ARN of the DynamoDB table"
}

output "dynamodb_table_arn" {
  value       = aws_dynamodb_table.terraform_lock.arn
  description = "The ARN of the DynamoDB table"
}

来回切换

您可能会问自己,我们如何使用 Terraform 来设置我们想用于远程状态后端的 S3 存储桶和 DynamoDB 表? 我们有几种方法可以做到这一点

  • 如果您有共享的本地状态,则需要将本地状态提交到您的 VCS 并将其共享到远程存储库中。
  • 您可以执行状态迁移。 首先,将本地状态迁移到远程状态后端。

这两种解决方案都涉及使用 local state 创建 remote state 资源。 请记住,Terraform 状态可能包含机密信息。 如果仅是 Amazon S3 存储桶和 DynamoDB 表,则只有一个变量可能存在问题:AWS 访问密钥

机密信息

在 Terraform 中,如果您使用的是私有存储库,那么这可能不是什么大问题。 在处理开源代码时,在将状态文件提交到 GitHub 或您使用的任何 VCS 到存储库之前对其进行加密可能会很有用。 您可以使用 OpenSSL 甚至更专门的工具来执行此操作。

堆栈

我们目前使用的是

  • Terraform
  • Jekyll
  • Git

让我们在本例中创建两个工作区 stateprod。 state 工作区将管理远程状态资源,即 S3 存储桶和 DynamoDB 表。 prod 工作区将管理我们网站的生产环境。 稍后您可以添加更多工作区用于暂存或测试,但这超出了本文的范围。

引导

让我们创建三个包含 Terraform 文件的文件夹,

  • Bootstrap
  • Backend
  • Website

现在,这就是您要关注的项目的结构

.
├── locals.tf
├── providers.tf
├── backend
│   ├── backend.tf
│   ├── backend.tf.tmpl
│   ├── locals.tf -> ../locals.tf
│   ├── providers.tf -> ../providers.tf
│   └── state.tf -> ../bootstrap/state.tf
├── bootstrap
│   ├── locals.tf -> ../locals.tf
│   ├── providers.tf -> ../providers.tf
│   └── state.tf
└── website
    ├── backend.tf -> ../backend/backend.tf
    ├── locals.tf -> ../locals.tf
    ├── providers.tf -> ../providers.tf
    └── website.tf

让我们看一下 bootstrap/state.tf 的内容

# Montana's config files /bootstrap/state.tf

locals {
  state_bucket_name = "${local.project_name}-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}"
  state_table_name = "${local.state_bucket_name}"
}

resource "aws_dynamodb_table" "locking" {
  name           = "${local.state_table_name}"
  read_capacity  = "20"
  write_capacity = "20"
  hash_key       = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

resource "aws_s3_bucket" "state" {
  bucket = "${local.state_bucket_name}"
  region = "${data.aws_region.current.name}"

  versioning {
    enabled = true
  }

  server_side_encryption_configuration {
    "rule" {
      "apply_server_side_encryption_by_default" {
        sse_algorithm = "AES256"
      }
    }
  }

  tags {
    Name = "terraform-state-bucket"
    Environment = "global"
    project = "${local.project_name}"
  }
}

output "BACKEND_BUCKET_NAME" {
  value = "${aws_s3_bucket.state.bucket}"
}

output "BACKEND_TABLE_NAME" {
  value = "${aws_dynamodb_table.locking.name}"
}

我在上面定义了 S3 存储桶,启用了加密和版本控制。 加密很重要,因为 Terraform 通常会包含 secrets

您会在 Terraform 中注意到我调用了一个名为 LockID 的属性,因此我们必须创建它并将其设为主键,以再次确保不会出现两个 Terraform 实例同时运行的情况。

让我们通过运行以下命令来创建 state 工作区

terraform workspace new state
terraform init bootstrap
terraform apply bootstrap

之后,将创建 S3 存储桶和 DynamoDB 表,我们将迁移本地状态。 让我们看一下 backend/backend.tf.tmpl 文件,这是 Terraform 将遵循的配置文件,您可以生成一个环境变量,或者在我的情况下,我从键值对设置了环境变量。 如果您不想采用传统方法,可以使用我提供的这个巧妙的 bash 脚本

#!/bin/bash
dotenv () {
  set -a
  [ -f .env ] && . .env
  set +a
}

dotenv

cd () {
  builtin cd $@
  dotenv
}

现在,配置文件应该如下所示

terraform {
  backend "s3" {
    bucket         = "${BACKEND_BUCKET_NAME}"
    key            = "terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "${BACKEND_TABLE_NAME}"
  }
}

让我们初始化后端,运行

terraform init backend

现在您应该拥有一些核心资产,现在我们需要指定 HTML 和 CSS 文件。 这有点麻烦,因为我们无法像使用 wgetcurl 一样告诉 Terraform 上传整个文件夹,但这正是 Terraform 的结构,让我们看一下 website.tf

locals {
  site_root = "website/static/_site"
  index_html = "${local.site_root}/index.html"
  about_html = "${local.site_root}/about/index.html"
  post_html = "${local.site_root}/jekyll/update/2018/06/30/welcome-to-jekyll.html"
  error_html = "${local.site_root}/404.html"
  main_css = "${local.site_root}/assets/main.css"
}

resource "aws_s3_bucket_object" "index" {
  bucket = "${aws_s3_bucket.website.id}"
  key    = "index.html"
  source = "${local.index_html}"
  etag   = "${md5(file(local.index_html))}"
  content_type = "text/html"
}

resource "aws_s3_bucket_object" "post" {
  bucket = "${aws_s3_bucket.website.id}"
  key    = "jekyll/update/2018/06/30/welcome-to-jekyll.html"
  source = "${local.post_html}"
  etag   = "${md5(file(local.post_html))}"
  content_type = "text/html"
}

resource "aws_s3_bucket_object" "about" {
  bucket = "${aws_s3_bucket.website.id}"
  key    = "about/index.html"
  source = "${local.about_html}"
  etag   = "${md5(file(local.about_html))}"
  content_type = "text/html"
}

resource "aws_s3_bucket_object" "error" {
  bucket = "${aws_s3_bucket.website.id}"
  key    = "error.html"
  source = "${local.error_html}"
  etag   = "${md5(file(local.error_html))}"
  content_type = "text/html"
}

resource "aws_s3_bucket_object" "css" {
  bucket = "${aws_s3_bucket.website.id}"
  key    = "assets/main.css"
  source = "${local.main_css}"
  etag   = "${md5(file(local.main_css))}"
  content_type = "text/css"
}

output "url" {
  value = "http://${local.website_bucket_name}.s3-website.${aws_s3_bucket.website.region}.amazonaws.com"
}

现在让我们运行一些最终命令

terraform workspace new prod
terraform init website
cd website/static && jekyll build && cd -
terraform apply website

我们现在已经使用 Terraform 和 DynamoDB 创建了一个静态网站。 现在让我们实现 Travis!

Travis

让我们开始实现 Travis 来捕获任何错误! 您知道我们需要创建 .travis.yml.,简而言之,它会告诉构建服务器要执行哪些命令。 让我们看一下 .travis.yml 文件

---
language: generic # this can also be left out

install:
  - gem install bundler jekyll

script:
  - ./build.sh

我们需要那个 build.sh 文件,但请记住要为它赋予适当的权限,以下是其内容

cd website/static
bundle install
bundle exec jekyll build
cd -

./terraform-linux init
./terraform-linux validate website

if [[ $TRAVIS_BRANCH == 'master' ]]
then
    ./terraform-linux workspace select prod
    ./terraform-linux apply -auto-approve website
fi

结论

如果可能的话,我可能会选择仅使用本地状态并将它直接存储在存储库中,这将使许多步骤变得更轻松,幸运的是,我专门为 Terraform 和 Travis 创建了一个 bash 脚本,以简化操作。 这是针对测试环境的,因此请确保在使用它之前,您是在暂存环境或某种测试环境中运行它。

#!/bin/bash
set=+x

/ # set -v && echo $HOME
/root
/ # set +v && echo $HOME
set +v && echo $HOME
/root

/ # set -x && echo $HOME
+ echo /root
/root
/ # set +x && echo $HOME
+ set +x
/root

# Check the creds made sure by Montana Mendy. 

cred="-var "do_token=${DO_PAT}""
tf=$(which terraform)

init() {
    echo ""
    echo "Init the provider"
    echo ""
    $tf init

    echo "Formatting files"
    echo ""
    $tf fmt
    echo ""
    echo "Validating files"
    echo ""
    $tf validate
}

clone() {
    if [ -d ansible ]; then
        rm -rf ansible
        git clone --depth=1 $ansible_repo
        echo ""
    else
        git clone --depth=1 $ansible_repo
        echo ""
    fi

    if [ -d shell_scripts ]; then
        rm -rf shell_scripts
        git clone --depth=1 $shell_script_repo
        echo ""
    else
        git clone --depth=1 $shell_script_repo
        echo ""
    fi

    if [ -d cloud_init ]; then
        rm -rf cloud_init
        git clone --depth=1 $cloud_init_repo
        echo ""
    else
        git clone --depth=1 $cloud_init_repo
        echo ""
    fi
}

# Clean up shell scripts.

clean() {
    if [ -d shell_scripts ]; then
        rm -rf shell_scripts
    fi
    if [ -d ansible ]; then
        rm -rf ansible
        if [ -f inventory ]; then
            rm inventory
        fi
    fi
    if [ -d cloud_init ]; then
        rm -rf cloud_init
    fi
    if [ -f terraform.tfstate ]; then
        rm -rf terraform*
        rm -rf .terraform
    fi
}

# Run Terraform commands.

plan() {
    clone
    init
    $tf plan $cred
}

apply() {
    clone
    init
    $tf apply -auto-approve $cred
}

case $1 in
-i | init)
    $tf init
    ;;
clone)
    clone
    ;;
plan)
    plan
    ;;
apply)
    apply
    ;;
show)
    $tf show
    ;;
list)
    $tf state list
    ;;
    
# Run 'terraform destroy'. 
    
destroy)
    $tf destroy $cred
    clean
    ;;
clean)
    clean
    ;;
-f | fmt)
    $tf fmt
    ;;
-v | validate)
    $tf validate
    ;;
*)
    echo "$0 [options]"
    echo ""
    echo "Options are: clone | plan | apply| show| list | destroy | fmt | validata"
    echo ""
    ;;
esac

好了,我们使用 Terraform、Travis CI、Amazon、API Gateway 创建并部署了一个 Jekyll 网站,还附带了我的一些提示和技巧,我认为您在使用它进行配置时可能会发现这些提示和技巧很有用。

如果您有任何问题,请发送电子邮件至 [email protected].

一如既往,祝您构建愉快!

© 版权所有 2024,保留所有权利
© 版权所有 2024,保留所有权利
© 版权所有 2024,保留所有权利