云简历挑战(AWS)
#python #devops #云 #terraform

云简历挑战是一个项目,可帮助我们学习使用云和一些基本工具。您可以在此处访问项目的轮廓。

在这篇博客文章中,我将告诉您我完成的步骤,以及我经历了Cloud Resume Challenge的挑战。这个挑战使我更熟悉与Terraform一起使用AWS服务。我了解了服务的工作原理。我还学到了很多我现在想不到的事情。

您可以看到最终结果here并查看GitHub code

您可以作为AWS Free Tier的一部分完成此挑战。
您只需要购买域名,但是如果您是学生,则有很多免费购买的方法。在本文的下一部分中,我将讨论如何获得免费域名。

Projecy Diagram

现在,让我们看一下我们需要完成的阶段。

挑战步骤

  • 在html/css中构建网站。

此步骤非常容易。只需使用HTML和CSS创建简历网站的简历页。

  • 带有S3桶的主机网站。

我正在使用Terraform构建,但是您可以使用AWS SAM。

我创建了一个S3存储桶并确定了必要的CORS规则,最后,我确保将对象集体上传到S3桶。

resource "aws_s3_bucket" "cloud-resume-bucket" {
  bucket = var.bucket_name
  acl    = "public-read"
  policy = file("website/policy.json")

  website {
    index_document = "index.html"
    error_document = "error.html"
  }
}


resource "aws_s3_bucket_cors_configuration" "s3_bucket_cors" {
  bucket = aws_s3_bucket.cloud-resume-bucket.id

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "POST"]
    allowed_origins = ["*"]
    max_age_seconds = 10
  }
}


resource "aws_s3_object" "test" {
  for_each = fileset("${path.module}/html", "**/*.*")
  acl    = "public-read"
  bucket = var.bucket_name
  key    = each.value
  source = "${path.module}/html/${each.value}"
  content_type  = lookup(var.mime_types, split(".", each.value)[length(split(".", each.value)) - 1])
}
  • 用于路由http/s流量的云方。

S3网站URL应将HTTPS用于安全性。我用Cloudfront做到了。

resource "aws_cloudfront_distribution" "s3_cf" {
  origin {
    domain_name              = "${aws_s3_bucket.cloud-resume-bucket.bucket_regional_domain_name}"
    origin_id                = "${local.s3_origin_id}"
  }

  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"


  custom_error_response {
      error_caching_min_ttl = 0
      error_code = 404
      response_code = 200
      response_page_path = "/error.html"
  }

  aliases = [var.domain_name]

  default_cache_behavior {
    allowed_methods  = ["GET", "HEAD"]
    cached_methods   = ["GET", "HEAD"]
    target_origin_id = "${local.s3_origin_id}"

    forwarded_values {
      query_string = false

      cookies {
        forward = "none"
      }
    }

    viewer_protocol_policy = "redirect-to-https" #redirect-to-https
    min_ttl                = 0
    default_ttl            = 3600
    max_ttl                = 86400
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }

  # viewer_certificate {
  #   cloudfront_default_certificate = true
  # }

  viewer_certificate {
    acm_certificate_arn = aws_acm_certificate_validation.acm_val.certificate_arn
    ssl_support_method = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

}

  • Route53用于自定义DNS。

在此步骤中,我将我的域名注册到Route53服务中,如下所示。
(使用GitHub学生包,您可以获取一个免费的域名。)

resource "aws_route53_zone" "main" {
  name = var.domain_name
}

resource "aws_route53_record" "domain" {
  zone_id = "${aws_route53_zone.main.zone_id}"
  name = "${var.domain_name}"
  type = "A"

  alias {
    name = "${aws_cloudfront_distribution.s3_cf.domain_name}"
    zone_id = "${aws_cloudfront_distribution.s3_cf.hosted_zone_id}"
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = aws_route53_zone.main.zone_id
}
  • 证书经理,用于使用SSL证书启用安全访问。

我设置了ACM服务的SSL证书。

resource "aws_acm_certificate" "cert" {
  domain_name       = var.domain_name
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_acm_certificate_validation" "acm_val" {
  certificate_arn         = aws_acm_certificate.cert.arn
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}

  • DynamoDB用于数据库,存储网站访问者数量。

我创建了一个DynamoDB数据库来检索和更新访问者计数器。

resource "aws_dynamodb_table" "visiters" {
  name           = var.dynamodb_table
  billing_mode   = "PROVISIONED"
  read_capacity  = 1
  write_capacity = 1
  hash_key       = "id"

  attribute {
    name = "id"
    type = "N"
  }
}
  • lambda函数(Python)读取/写网站访问者数量到dynamodb。

将其设置为使用数据库后。我添加了与Python(Boto3)和必要的IAM策略一起编写的Lambda功能。

data "archive_file" "lambda_zip" {
  type = "zip"

  source_dir  = "${path.module}/src"
  output_path = "${path.module}/src.zip"
}    

resource "aws_s3_object" "this" {
  bucket = aws_s3_bucket.cloud-resume-bucket.id

  key    = "src.zip"
  source = data.archive_file.lambda_zip.output_path

  etag = filemd5(data.archive_file.lambda_zip.output_path)
}

//Define lambda function
resource "aws_lambda_function" "apigw_lambda_ddb" {
  function_name = "app"
  description = "visiter counter"

  s3_bucket = aws_s3_bucket.cloud-resume-bucket.id
  s3_key    = aws_s3_object.this.key

  runtime = "python3.8"
  handler = "app.lambda_handler"

  source_code_hash = data.archive_file.lambda_zip.output_base64sha256

  role = aws_iam_role.lambda_exec.arn

  environment {
    variables = {
      DDB_TABLE = var.dynamodb_table
    }
  }


} 

resource "aws_iam_role" "lambda_exec" {
  name_prefix = "LambdaDdbPost"

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

resource "aws_iam_policy" "lambda_exec_role" {
  name_prefix = "lambda-tf-pattern-ddb-post"

  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem",
                "dynamodb:UpdateItem"
            ],
            "Resource": "arn:aws:dynamodb:*:*:table/${var.dynamodb_table}"
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "lambda_policy" {
  role       = aws_iam_role.lambda_exec.name
  policy_arn = aws_iam_policy.lambda_exec_role.arn
}
  • API网关触发lambda函数。

我设置了触发lambda func的API网关。

# resource "random_string" "random" {
#   length           = 4
#   special          = false
# }

resource "aws_apigatewayv2_api" "http_lambda" {
  # name          = "${var.apigw_name}-${random_string.random.id}"
  name          = "${var.apigw_name}"
  protocol_type = "HTTP"
}

resource "aws_apigatewayv2_stage" "default" {
  api_id = aws_apigatewayv2_api.http_lambda.id

  name        = "$default"
  auto_deploy = true
}

resource "aws_apigatewayv2_integration" "apigw_lambda" {
  api_id = aws_apigatewayv2_api.http_lambda.id

  integration_uri    = aws_lambda_function.apigw_lambda_ddb.invoke_arn
  integration_type   = "AWS_PROXY"
  integration_method = "POST"
}

resource "aws_apigatewayv2_route" "get" {
  api_id = aws_apigatewayv2_api.http_lambda.id

  route_key = "GET /" 
  target    = "integrations/${aws_apigatewayv2_integration.apigw_lambda.id}"
}

# Gives an external source permission to access the Lambda function.
resource "aws_lambda_permission" "api_gw" {                            
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.apigw_lambda_ddb.function_name
  principal     = "apigateway.amazonaws.com"

  source_arn = "${aws_apigatewayv2_api.http_lambda.execution_arn}/*/*"
}

然后,在使用GitHub操作设置CI/CD流程后,我启动了网站。这篇博客文章只是我工作的摘要。完成此操作时,我遇到了许多困难。对我来说,最困难的一步是连接lambda -apigw -dynamoDB服务,但是经过一些思考和研究,我能够解决这个问题。感谢您的阅读,如果您想详细介绍项目的代码,可以访问我的GitHub帐户。

My Resume Site
GitHub Link of The Project