【最終回】静的Webサイトホスティング二重化してみる(その4)

静的Webサイトホスティングパターンの二重化、前回から半年近くが経過しました。色々と試行錯誤していてようやくCloudFormationとかDeploymentManagerとかTerraformもなれてきたので宿題だったIaC化にチャレンジしました。

前提条件としてDockerでAWS-CLIとかGoogleCloud-SDK丸ごと込々の環境を作成できていることが必要になります。とりあえずそちらはDockerでサクッと作りました

ほんとはGitHubのところまでできたらよかったんですが、そこはスキルがなくてヨワヨワなのでここまでが限界でした。ひとまず静的Webサイトホスティングパターンはここまでで最後です。ようやく昨年からの懸案事項解決できました。


◆シリーズ

その1:AWSでCloudFront+S3構成を作る

その2:GoocleCloudでLoadBaranser+GCS構成を作りDNSでフェイルオーバ

その3:S3とGCSにGitHubActions使ってコンテンツ同期

その4:TerraformでIaC化


◆設定作業編

1.Docker内初期設定

(1)AWS初期設定

#コンテナログイン
$ docker exec -it terraform-dev /bin/bash

#AWS認証情報
# aws configure
AWS Access Key ID [None]: INPUTACCESSKEY
AWS Secret Access Key [None]: INPUTSECRETACCESSKEY
Default region name [None]: ap-northeast-1
Default output format [None]: json

#環境変数設定
# cd ~/terraform/
# export TF_VAR_access_key="ACCESSKEY"
# export TF_VAR_secret_key="SECRETACCESSKEY"
# export TF_VAR_role_arn="ROLE/ARN"

(2)GCP認証情報

#初期設定
# gcloud init
Welcome! This command will take you through the configuration of gcloud.
~中略~
You must log in to continue. Would you like to log in (Y/n)? Y
~中略~

Pick cloud project to use:
~中略~
Please enter numeric choice or text value (must exactly match list
item):7
~中略~
Do you want to configure a default Compute Region and Zone? (Y/n)? Y
~中略~
Please enter numeric choice or text value (must exactly match list
item):  34
 files and output formatting

#プロジェクト設定
# export GCP_PROJECT_ID=$(gcloud config get-value project)
# env | grep GCP_PROJECT_ID
G
#サービスアカウント作成
# gcloud iam service-accounts create terraform-serviceaccount --display-name "Account for Terraform"
# gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID} --member serviceAccount:terraform-serviceaccount@${GCP_PROJECT_ID}.iam.gserviceaccount.com --role roles/editor

#クレデンシャルキー発行
# gcloud iam service-accounts keys create ~/.config/gcloud/sakey.json \
  --iam-account hoge@project.iam.gserviceaccount.com

#環境変数設定
# export GOOGLE_CLOUD_KEYFILE_JSON=~/.config/gcloud/sakey.json
# export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)

(3)Azure認証情報

#初期設定 クレデンシャル発行
# az login
~中略~
・認証情報取得
# az account list --query "[].{name:name, subscriptionId:id, tenantId:tenantId}"
~中略~
# az account set --subscription="subscriptionID"
# az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/subscriptionID"
Creating 'Contributor' role assignment under scope '/subscriptions/subscriptionID'
The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
{
  "appId": "appId",
  "displayName": "azure-XXXX",
  "name": "http://azure-XXXX",
  "password": "password",
  "tenant": "tenant"
}

・環境変数設定
# export ARM_SUBSCRIPTION_ID=subscriptionId
# export ARM_CLIENT_ID=appId
# export ARM_CLIENT_SECRET=password
# export ARM_TENANT_ID=tenant
# export ARM_ENVIRONMENT=public

2.Terraform

2.1.ファイル群

(1)ルート配下

#main.tf
-------------------
provider "aws" {
  region = "ap-northeast-1"
}
provider "aws" {
  alias  = "use1"
  region = "us-east-1"
}

provider "google" {
  project     = var.googleCloud.project 
  credentials = file(var.googleCloud.credentials)
}

provider "google-beta" {
  project     = var.googleCloud.project 
  credentials = file(var.googleCloud.credentials)
}

module "zone_acm" {
  source              = "./zone_acm"
  root_domain = var.root_domain
    providers = {
        aws = aws.use1
    }
}

module "awsstatic" {
  source              = "./awsstatic"
  root_domain = var.root_domain
  site_domain = var.site_domain
  bucket_name = var.name
  acm_cert = module.zone_acm.cert_arn
  dns_zone = module.zone_acm.domain_zone
  depends_on = [module.zone_acm]
}

module "gcpstatic" {
  source              = "./gcpstatic"
  googleCloud = var.googleCloud
  googlebucket = var.googlebucket
  name = var.name
  root_domain = var.root_domain
  site_domain = var.site_domain
  dns_zone = module.zone_acm.domain_zone
  depends_on = [module.awsstatic]
}

module "failoverdns" {
  source              = "./failoverdns"
  root_domain = var.root_domain
  site_domain = var.site_domain
  bucket_name = var.name
  dns_zone = module.zone_acm.domain_zone
  cloudfront_id = module.awsstatic.cloudfront_alias_id
  cloudfront_domain = module.awsstatic.cloudfront_alias_name
  gcp_ipv4_addr = module.gcpstatic.gcp_ipv4
  gcp_ipv6_addr = module.gcpstatic.gcp_ipv6
  depends_on = [module.gcpstatic]
}

#variables.tf
-------------------
variable "site_domain" {
  default = "www.yourdomain"
}
variable "root_domain" {
  default = "yourdomain"
}
variable "name" {
  default = "yourdomain"
}

variable "googleCloud" {
  default = {
     project = "youre_GCP_projectId"
     credentials = "~/.config/gcloud/sakey.json"
  }
}

variable "googlebucket" {
  default = {
    bucket_name   = "bucketname"
    region        = "asia"
    storage_class = "MULTI_REGIONAL"
    log_region    = "asia-northeast1"
    log_class     = "NEARLINE"
  }
}
-------------------

(2)zoneacm配下

#main.tf
-------------------
resource "aws_acm_certificate" "cert" {
  domain_name       = var.root_domain
  validation_method = "DNS"

  subject_alternative_names = [
    var.root_domain,
    "*.${var.root_domain}"
  ]
  tags = {
    ManagedBy = "terraform"
    Changed   = formatdate("YYYY-MM-DD hh:mm ZZZ", timestamp())
  }
  lifecycle {
    ignore_changes = [tags]
  }
}

resource "aws_route53_zone" "zone" {
  name = var.root_domain
  tags = {
    ManagedBy = "terraform"
    Changed   = formatdate("YYYY-MM-DD hh:mm ZZZ", timestamp())
    Name      = var.root_domain
  }
}

resource "null_resource" "get_ns_records" {
  provisioner "local-exec" {
    command = "/bin/bash ./sh/get_ns_records.sh ${var.root_domain}"
  }
}

resource "aws_route53_record" "validations" {
  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.zone.zone_id
}

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

#variables.tf
-------------------
variable "root_domain" {}
-------------------

#output.tf
-------------------
output "domain_zone" {
  value = aws_route53_zone.zone.zone_id
}

output "cert_arn" {
  value = aws_acm_certificate.cert.arn
}
-------------------

(3)awsstatic配下

#s3.tf
-------------------
data "aws_iam_policy_document" "cloudfront-logging-bucket" {
  statement {
    sid    = ""
    effect = "Allow"
    principals {
      identifiers = ["*"]
      type        = "*"
    }
    actions = [
      "s3:ListBucket",
      "s3:PutObject",
      "s3:GetObject"
    ]
    resources = [
      "arn:aws:s3:::${var.bucket_name}-cloudfront-logs",
      "arn:aws:s3:::${var.bucket_name}-cloudfront-logs/*"
    ]
  }
}

resource "aws_s3_bucket" "cloudfront-logging" {
  bucket        = "${var.bucket_name}-cloudfront-logs"
  policy        = data.aws_iam_policy_document.cloudfront-logging-bucket.json
  request_payer = "BucketOwner"
}

resource "aws_s3_bucket_public_access_block" "cloudfront-logging" {
  bucket                  = aws_s3_bucket.cloudfront-logging.bucket
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket" "website-logs" {
  bucket = "${var.bucket_name}-logs"
  acl    = "log-delivery-write"
  tags = {
    ManagedBy = "terraform"
    Changed   = formatdate("YYYY-MM-DD hh:mm ZZZ", timestamp())
  }
  lifecycle {
    ignore_changes = [tags]
  }
}

resource "aws_s3_bucket" "static-site" {
  bucket = var.bucket_name
  acl    = "private"
  policy = data.aws_iam_policy_document.static-site-bucket_policy.json
  versioning {
    enabled = true
  }
  logging {
    target_bucket = aws_s3_bucket.website-logs.bucket
    target_prefix = "${var.bucket_name}/"
  }
  lifecycle {
    ignore_changes = [tags]
  }
}

locals {
  s3-origin-id-static-site = "s3-origin-id-static-site"
}

data "aws_iam_policy_document" "static-site-bucket_policy" {
  statement {
    sid    = ""
    effect = "Allow"
    principals {
      type        = "AWS"
      identifiers = [aws_cloudfront_origin_access_identity.static-site-idntity.iam_arn]
    }
    actions = [
      "s3:GetObject"
    ]
    resources = [
      "arn:aws:s3:::${var.bucket_name}",
      "arn:aws:s3:::${var.bucket_name}/*"
    ]
  }
}
-------------------

#cloudfront.tf
-------------------
resource "aws_cloudfront_distribution" "static-site-dst" {
  origin {
    domain_name = aws_s3_bucket.static-site.bucket_regional_domain_name
    origin_id   = local.s3-origin-id-static-site
    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.static-site-idntity.cloudfront_access_identity_path
    }
  }

  enabled             = true
  is_ipv6_enabled     = true
  comment             = var.root_domain
  default_root_object = "index.html"

  logging_config {
    include_cookies = false
    bucket          = aws_s3_bucket.cloudfront-logging.bucket_regional_domain_name
    prefix          = "cloudfront"
  }

  aliases = [
    var.root_domain,
    "www.${var.root_domain}",
    "main.${var.root_domain}"
  ]

  viewer_certificate {
    acm_certificate_arn            = var.acm_cert
    minimum_protocol_version       = "TLSv1.2_2019"
    ssl_support_method             = "sni-only"
    cloudfront_default_certificate = false
  }

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

  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = local.s3-origin-id-static-site
    compress               = true
    viewer_protocol_policy = "redirect-to-https"
    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}

resource "aws_cloudfront_origin_access_identity" "static-site-idntity" {
  comment = "access-identity-static-site.s3.amazonaws.com"
}

resource "aws_route53_record" "root_domain" {
  zone_id = var.dns_zone
  name = var.root_domain
  type = "A"
  alias {
    name = aws_cloudfront_distribution.static-site-dst.domain_name
    zone_id = aws_cloudfront_distribution.static-site-dst.hosted_zone_id
    evaluate_target_health = false
  }
}

resource "aws_route53_record" "www_domain" {
  zone_id = var.dns_zone
  name = var.site_domain
  type = "A"
  alias {
    name = aws_cloudfront_distribution.static-site-dst.domain_name
    zone_id = aws_cloudfront_distribution.static-site-dst.hosted_zone_id
    evaluate_target_health = false
  }
}
-------------------

#variables.tf
-------------------
variable "root_domain" {}
variable "site_domain" {}
variable "bucket_name" {}
variable "acm_cert" {}
variable "dns_zone" {}
-------------------

#output.tf
-------------------
output "cloudfront_alias_id" {
  value = aws_cloudfront_distribution.static-site-dst.hosted_zone_id
}

output "cloudfront_alias_name" {
  value = aws_cloudfront_distribution.static-site-dst.domain_name
}

output "cloudfront_dist_id" {
  value = aws_cloudfront_distribution.static-site-dst.id
}
-------------------

(4)gcpstatic配下

#cert.tf
-------------------
resource "google_compute_managed_ssl_certificate" "naked_lb_cert" {
  name = "${var.name}-cert"
  managed {
    domains = [var.root_domain]
  }
}

resource "google_compute_managed_ssl_certificate" "www_lb_cert" {
  name = "www-${var.name}-cert"
  managed {
    domains = [var.site_domain]
  }
}
-------------------

#gcs.tf
-------------------
resource "google_storage_bucket" "logs-bucket" {
  name          = "${var.googlebucket.bucket_name}-logs"
  location      = var.googlebucket.log_region
  storage_class = var.googlebucket.log_class
  force_destroy = true
  labels = {
    project = var.googleCloud.project
  }
}

resource "google_storage_bucket_acl" "logs-bucket_iam" {
  bucket = google_storage_bucket.logs-bucket.name
  role_entity = [
    "WRITER:group-cloud-storage-analytics@google.com",
  ]
  default_acl = "projectprivate"
}

resource "google_storage_bucket" "bucket" {
  name          = var.googlebucket.bucket_name
  location      = var.googlebucket.region
  storage_class = var.googlebucket.storage_class
  force_destroy = true
  labels = {
    project = var.googleCloud.project
  }
  versioning {
    enabled = true
  }
  website {
    main_page_suffix = "index.html"
    not_found_page   = "404.html"
  }
  logging {
    log_bucket        = "${var.googlebucket.bucket_name}-logs"
  }
  uniform_bucket_level_access = true
}

resource "google_storage_bucket_iam_member" "staticweb_bucket_iam_member_object_viewer" {
  bucket = google_storage_bucket.bucket.name
  role   = "roles/storage.legacyObjectReader"
  member = "allUsers"
}
-------------------

#loadbalancer.tf
-------------------
resource "google_compute_backend_bucket" "bucket_backend" {
  name        = "${var.name}-backend"
  bucket_name = google_storage_bucket.bucket.name
}

resource "google_compute_url_map" "urlmap" {
  name        = "${var.name}-https-site"
  description = "URL map for ${var.name}"
  default_service = google_compute_backend_bucket.bucket_backend.self_link
  host_rule {
    hosts        = ["*"]
    path_matcher = "all"
  }
  path_matcher {
    name            = "all"
    default_service = google_compute_backend_bucket.bucket_backend.self_link
    path_rule {
      paths   = ["/*"]
      service = google_compute_backend_bucket.bucket_backend.self_link
    }
  }
}

resource "google_compute_target_https_proxy" "https_proxy" {
  name        = "${var.name}-proxy"
  url_map     = google_compute_url_map.urlmap.self_link
  ssl_certificates = [
    google_compute_managed_ssl_certificate.naked_lb_cert.id,
    google_compute_managed_ssl_certificate.www_lb_cert.id
  ]
}

resource "google_compute_global_forwarding_rule" "https_ipv4" {
  name   = "${var.name}-v4-fwdrule"
  target = google_compute_target_https_proxy.https_proxy.id
  port_range = "443"
  ip_address = google_compute_global_address.lb_v4_address.address
}

resource "google_compute_global_forwarding_rule" "https_ipv6" {
  name   = "${var.name}-v6-fwdrule"
  target = google_compute_target_https_proxy.https_proxy.id
  port_range = "443"
  ip_address = google_compute_global_address.lb_v6_address.address
}

resource "google_compute_url_map" "http_redirect" {
  name = "${var.name}-redirect"
  default_url_redirect {
    redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
    https_redirect         = true
    strip_query            = false
  }
}

resource "google_compute_target_http_proxy" "http_default" {
  name     = "${var.name}-https-redirect-proxy"
  url_map  = google_compute_url_map.http_redirect.self_link
}

resource "google_compute_global_forwarding_rule" "http_ipv4" {
  name       = "${var.name}-http-v4-fwdrule"
  target     = google_compute_target_http_proxy.http_default.self_link
  ip_address = google_compute_global_address.lb_v4_address.address
  port_range = "80"
}

resource "google_compute_global_forwarding_rule" "http_ipv6" {
  name       = "${var.name}-http-v6-fwdrule"
  target     = google_compute_target_http_proxy.http_default.self_link
  ip_address = google_compute_global_address.lb_v6_address.address
  port_range = "80"
}

resource "null_resource" "check_complete_lb" {
  provisioner "local-exec" {
    command = "/bin/bash ./sh/gcp_cert_check.sh ${var.root_domain}"
  }
  depends_on = [
    aws_route53_record.gcp_root_domain,
    aws_route53_record.gcp_www_domain
  ]
}
-------------------

#gcpip.tf
-------------------
resource "google_compute_global_address" "lb_v4_address" {
  name = "lb-v4-address"
  ip_version = "IPV4"
}

resource "google_compute_global_address" "lb_v6_address" {
  name       = "lb-v6-address"
  ip_version = "IPV6"
}

resource "null_resource" "delete_aliasrecords" {
  provisioner "local-exec" {
    command = "/bin/bash ./sh/alias_arecord_delete.sh ${var.root_domain}"
  }
  depends_on = [
    google_compute_global_address.lb_v4_address,
    google_compute_global_address.lb_v6_address
  ]
}

resource "aws_route53_record" "gcp_root_domain" {
  zone_id = var.dns_zone
  name = var.root_domain
  type = "A"
  ttl = "60"
  records = [google_compute_global_address.lb_v4_address.address]
  depends_on = [null_resource.delete_aliasrecords]
}

resource "aws_route53_record" "gcp_www_domain" {
  zone_id = var.dns_zone
  name = var.site_domain
  type = "A"
  ttl = "60"
  records = [google_compute_global_address.lb_v4_address.address]
  depends_on = [null_resource.delete_aliasrecords]
}
-------------------

#variables.tf
-------------------
variable "site_domain" {}
variable "root_domain" {}
variable "dns_zone" {}
variable "name" {}
variable "googleCloud" {}
variable "googlebucket" {}
-------------------

#output.tf
-------------------
output "gcp_ipv4" {
  value = google_compute_global_address.lb_v4_address.address
}

output "gcp_ipv6" {
  value = google_compute_global_address.lb_v6_address.address
}
-------------------

(5)failoverdns配下

#main.tf
-------------------
resource "null_resource" "delete_a_records" {
  provisioner "local-exec" {
    command = "/bin/bash ./sh/ip_arecord_delete.sh ${var.root_domain}"
  }
}

resource "aws_route53_health_check" "cdn_healthcheck" {
  reference_name   = "${var.bucket_name}_check"
  fqdn              = var.cloudfront_domain
  failure_threshold = "3"
  type              = "HTTPS"
  port              = 443
  request_interval  = "30"
  tags = {
    Name = "${var.bucket_name}-web-check"
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "main_alias_ipv4" {
  zone_id = var.dns_zone
  name = "main.${var.root_domain}"
  type = "A"
  alias {
    name = var.cloudfront_domain
    zone_id = var.cloudfront_id
    evaluate_target_health = false
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "main_alias_ipv6" {
  zone_id = var.dns_zone
  name = "main.${var.root_domain}"
  type = "AAAA"
  alias {
    name = var.cloudfront_domain
    zone_id = var.cloudfront_id
    evaluate_target_health = false
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "root_domain" {
  zone_id = var.dns_zone
  name = var.root_domain
  type = "A"
  health_check_id = aws_route53_health_check.cdn_healthcheck.id
  set_identifier = "${var.bucket_name}-ipv4-main"
  failover_routing_policy {
    type = "PRIMARY"
  }
  alias {
    name = aws_route53_record.main_alias_ipv4.name
    zone_id = var.dns_zone
    evaluate_target_health = true
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "ipv6_root_domain" {
  zone_id = var.dns_zone
  name = var.root_domain
  type = "AAAA"
  health_check_id = aws_route53_health_check.cdn_healthcheck.id
  set_identifier = "${var.bucket_name}-ipv6-main"
  failover_routing_policy {
    type = "PRIMARY"
  }
  alias {
    name = aws_route53_record.main_alias_ipv6.name
    zone_id = var.dns_zone
    evaluate_target_health =  true
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "www_domain" {
  zone_id = var.dns_zone
  name = var.site_domain
  type = "A"
  health_check_id = aws_route53_health_check.cdn_healthcheck.id
  set_identifier = "${var.bucket_name}-www-ipv4-main"
  failover_routing_policy {
    type = "PRIMARY"
  }
  alias {
    name = aws_route53_record.main_alias_ipv4.name
    zone_id = var.dns_zone
    evaluate_target_health =  true
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "ipv6_www_domain" {
  zone_id = var.dns_zone
  name = var.site_domain
  type = "AAAA"
  health_check_id = aws_route53_health_check.cdn_healthcheck.id
  set_identifier = "${var.bucket_name}-www-ipv6-main"
  failover_routing_policy {
    type = "PRIMARY"
  }
  alias {
    name = aws_route53_record.main_alias_ipv6.name
    zone_id = var.dns_zone
    evaluate_target_health =  true
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "gcp_root_domain" {
  zone_id = var.dns_zone
  name = var.root_domain
  type = "A"
  ttl = "60"
  records = [var.gcp_ipv4_addr]
  set_identifier = "${var.bucket_name}-ipv4-sub"
  failover_routing_policy {
    type = "SECONDARY"
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "gcp_ipv6_root_domain" {
  zone_id = var.dns_zone
  name = var.root_domain
  type = "AAAA"
  ttl = "60"
  records = [var.gcp_ipv6_addr]
  set_identifier = "${var.bucket_name}-ipv6-sub"
  failover_routing_policy {
    type = "SECONDARY"
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "gcp_www_domain" {
  zone_id = var.dns_zone
  name = var.site_domain
  type = "A"
  ttl = "60"
  records = [var.gcp_ipv4_addr]
  set_identifier = "${var.bucket_name}-www-ipv4-sub"
  failover_routing_policy {
    type = "SECONDARY"
  }
  depends_on = [null_resource.delete_a_records]
}

resource "aws_route53_record" "gcp_ipv6_www_domain" {
  zone_id = var.dns_zone
  name = var.site_domain
  type = "AAAA"
  ttl = "60"
  records = [var.gcp_ipv6_addr]
  set_identifier = "${var.bucket_name}-www-ipv6-sub"
  failover_routing_policy {
    type = "SECONDARY"
  }
  depends_on = [null_resource.delete_a_records]
}
-------------------

#variables.tf
-------------------
variable "root_domain" {}
variable "site_domain" {}
variable "bucket_name" {}
variable "dns_zone" {}
variable "cloudfront_id" {}
variable "cloudfront_domain" {}
variable "gcp_ipv4_addr" {}
variable "gcp_ipv6_addr" {}
-------------------

(6)sh配下

#get_ns_records.sh
-------------------
#!/bin/bash

if [ $# != 1 ]; then
    echo 'Empty CertName! Please [./get_ns_records.sh yourdomain]'
    exit 1
fi
domain=$1

route53result=$(aws route53 list-hosted-zones-by-name --dns-name ${domain} | jq -r ".HostedZones[0].Id")
hz=$(echo ${route53result} | sed -e "s!/hostedzone/!!g")
if [ "$hz" = "" ]; then
    echo 'Fail get HostedZoneId'
    exit 1
fi

echo '*** Please Setting Your Domain Nameserve ***'
route53records=$(aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/${hz})
array=$(echo ${route53records} | \
        jq -c -r '.ResourceRecordSets[]| if .Type == "NS" then .ResourceRecords[].Value else empty end'
)
for i in ${array[@]}
do
  echo ${i}
done
-------------------

#gcp_cert_check.sh
-------------------
#!/bin/bash
SLEEP_TIME=30
STATUS_CODE="ACTIVE"

if [ $# != 1 ]; then
    echo 'Empty CertName! Please [./gcp_cert_check.sh yourdomain]'
    exit 1
fi
domain=$1

# Check Cert status
certname=$(echo ${domain/./-})
echo "*** ssl cert status check ***"
while true
do
   echo "Check now please wait・・・"
   sleep ${SLEEP_TIME}
   naked_code=$(echo $(gcloud beta compute ssl-certificates describe ${certname}-cert \
    --global \
    --format="get(managed.domainStatus)") | cut -d= -f2)
   www_code=$(echo $(gcloud beta compute ssl-certificates describe www-${certname}-cert \
    --global \
    --format="get(managed.domainStatus)") | cut -d= -f2)
   if [ "$naked_code" = "$STATUS_CODE" ] && [ "$www_code" = "$STATUS_CODE" ]; then
      break
   fi
done
echo "*** Setting Complete ***"
-------------------

#alias_arecord_delete.sh
-------------------
#!/bin/bash
SLEEP_TIME=10
BREAK_WORD="INSYNC"
JSON_FILE=`mktemp`

if [ $# != 1 ]; then
    echo 'Empty Domain! Please [./alias_arecord_delete.sh yourdomain]'
    exit 1
fi
domainname=$1

# Get Hostedzoneid
route53result=$(aws route53 list-hosted-zones-by-name --dns-name ${domainname})
hosted=$(echo ${route53result} | jq -r ".HostedZones[0].Id")
hz=$(echo ${hosted} | sed -e "s!/hostedzone/!!g")

# Route53 Make TargetRecords
route53records=$(aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/${hz})
DELIMITA=","
RECTYPES=("A")

for (( i = 0; i < ${#RECTYPES[@]}; ++i )); do
if [ $i -gt 0  ]; then
  BODY+="$DELIMITA
  "
fi
route53alias=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[] | if .Type == "'${RECTYPES[$i]}'" then .AliasTarget else empty end')
route53dnsname=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[] | if .Type == "'${RECTYPES[$i]}'" then .Name else empty end')
RECORD_TYPE=${RECTYPES[$i]}
RESOURCE_VALUE=(`echo $route53alias`)
DNS_NAME=(`echo $route53dnsname`)

for (( j = 0; j < ${#RESOURCE_VALUE[@]}; ++j )); do
if [ $j -gt 0  ]; then
  BODY+="$DELIMITA"
fi
BODY+=$(cat < $JSON_FILE

# Deleting DNS Record set
jobid=$(aws route53 change-resource-record-sets --hosted-zone-id ${hz} --change-batch file://$JSON_FILE)
JOBID=$(echo ${jobid} | jq -r ".ChangeInfo.Id" | sed -e "s!/change/!!g")

#Status check
while true
do
  sleep ${SLEEP_TIME}
  route53stat=$(aws route53 get-change --id /change/${JOBID})
  stat=$(echo ${route53stat} | jq -r ".ChangeInfo.Status")
  if [ "$stat"=${BREAK_WORD} ]; then
    break
  fi
done
echo "*** Setting Complete ***"
-------------------

#ip_arecord_delete.sh
-------------------
#!/bin/bash
SLEEP_TIME=10
BREAK_WORD="INSYNC"
JSON_FILE=`mktemp`

if [ $# != 1 ]; then
    echo 'Empty Domain! Please [./ip_arecord_delete.sh yourdomain]'
    exit 1
fi
domainname=$1

# Get Hostedzoneid
route53result=$(aws route53 list-hosted-zones-by-name --dns-name ${domainname})
hosted=$(echo ${route53result} | jq -r ".HostedZones[0].Id")
hz=$(echo ${hosted} | sed -e "s!/hostedzone/!!g")

# Route53 Make TargetRecords
route53records=$(aws route53 list-resource-record-sets --hosted-zone-id /hostedzone/${hz})
DELIMITA=","
RECTYPES=("A")

for (( i = 0; i < ${#RECTYPES[@]}; ++i )); do
if [ $i -gt 0  ]; then
  BODY+="$DELIMITA
  "
fi
route53resourcevalue=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[] | if .Type == "'${RECTYPES[$i]}'" then .ResourceRecords[].Value else empty end')
route53dnsname=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[] | if .Type == "'${RECTYPES[$i]}'" then .Name else empty end')
RECORD_TYPE=${RECTYPES[$i]}
route53ttl=$(echo ${route53records} | jq -c -r '.ResourceRecordSets[] | if .Type == "'${RECTYPES[$i]}'" then .TTL else empty end')
RESOURCE_VALUE=(`echo $route53resourcevalue`)
DNS_NAME=(`echo $route53dnsname`)
TTL=(`echo $route53ttl`)

for (( j = 0; j < ${#RESOURCE_VALUE[@]}; ++j )); do
if [ $j -gt 0  ]; then
  BODY+="$DELIMITA"
fi

BODY+=$(cat < $JSON_FILE

# Deleting DNS Record set
jobid=$(aws route53 change-resource-record-sets --hosted-zone-id ${hz} --change-batch file://$JSON_FILE)
JOBID=$(echo ${jobid} | jq -r ".ChangeInfo.Id" | sed -e "s!/change/!!g")

#Status check
while true
do
  sleep ${SLEEP_TIME}
  route53stat=$(aws route53 get-change --id /change/${JOBID})
  stat=$(echo ${route53stat} | jq -r ".ChangeInfo.Status")
  if [ "$stat"=${BREAK_WORD} ]; then
    break
  fi
done
echo "*** Setting Complete ***"
-------------------


2.2.ディレクトリ構造

.
|-- awsstatic
|   |-- cloudfront.tf
|   |-- output.tf
|   |-- s3.tf
|   `-- variables.tf
|-- failoverdns
|   |-- main.tf
|   `-- variables.tf
|-- gcpstatic
|   |-- cert.tf
|   |-- gcpip.tf
|   |-- gcs.tf
|   |-- loadbalancer.tf
|   |-- output.tf
|   `-- variables.tf
|-- main.tf
|-- sh
|   |-- alias_arecord_delete.sh
|   |-- gcp_cert_check.sh
|   |-- get_ns_records.sh
|   `-- ip_arecord_delete.sh
|-- terraform.tfstate
|-- terraform.tfstate.backup
|-- variables.tf
`-- zone_acm
    |-- main.tf
    |-- output.tf
    `-- variables.tf

◆参考サイト

・terraform

https://dev.classmethod.jp/articles/terraform_module_coordination/

https://kazuhira-r.hatenablog.com/entry/2020/06/07/175759

https://medium.com/hashicorp-engineering/creating-module-dependencies-in-terraform-0-13-4322702dac4a

https://dev.classmethod.jp/articles/terraform013/

https://qiita.com/takkii1010/items/082c0854fd41bc0b26c3

・terraform特定リソース削除

https://www.devopsschool.com/blog/how-to-destroy-one-specific-resource-from-tf-file-in-terraform/

https://stackoverflow.com/questions/55265203/terraform-delete-all-resources-except-one

https://christina04.hatenablog.com/entry/2015/09/07/211925

https://kaoru2012.blogspot.com/2019/09/terraform_24.html

・terraformShell呼出し

https://dev.to/sagarjadhv23/executing-shell-script-in-terraform-via-null-resource-872

https://www.terraform.io/docs/language/resources/provisioners/local-exec.html

・S3+CloudFront+ACM

https://qiita.com/keitarounakano/items/11358d6814d7e9680054

https://qiita.com/neruneruo/items/cd550894aed76c11458c

https://github.com/cloudmaniac/terraform-aws-static-website/blob/master/main.tf

https://medium.com/anti-pattern-engineering/terraform%E3%82%92%E4%BD%BF%E3%81%84aws%E3%81%ABssl%E9%80%9A%E4%BF%A1%E3%81%A8%E7%8B%AC%E8%87%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Fstatic-website%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B%E8%A9%B1%E3%81%97-github-actions%E3%82%82%E3%81%82%E3%82%8B%E3%82%88-690f72f6f524

https://tech.lucheholdings.com/entry/2018/09/25/220855

https://qiita.com/matsuda-hiroki/items/25686218cafff6ed0989

https://katsuya-place.com/terraform-cloudfront/

・マルチリージョン

https://medium.com/@jyotti/terraform-multi-region-79a84ce1da0c

https://fusagiko.hatenablog.jp/entry/2019/12/01/advent_calendar

・Route53

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_health_check

https://blog.manabusakai.com/2020/04/change-the-routing-policy-in-terraform/

https://gside.org/blog/2016/10/16/

https://budougumi0617.github.io/2020/11/07/define_https_subdomain_by_terraform/

https://qiita.com/zembutsu/items/85ed9061288cae11bc21

https://dev.classmethod.jp/articles/route53-hostedzone-change-by-aws-cli/

https://qiita.com/iogi/items/676b8c5cbbb6ec874c37

https://techeten.xyz/1092/

・ACM

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation

https://qiita.com/y-ohgi/items/b823b6bd078ce8e11546

https://github.com/hashicorp/terraform-provider-aws/issues/8531

https://zenn.dev/shonansurvivors/articles/5ee0e50ab2c915

・CloudFront CLI操作

https://cloudpack.media/10762

https://www.semicolonworld.com/question/78733/aws-cli-disable-distribution

・GCP project関連

https://cloud.google.com/community/tutorials/managing-gcp-projects-with-terraform?hl=ja

・LB

https://qiita.com/ktoshi/items/a43a54005aa73be1254d

https://qiita.com/ktoshi/items/1fd3c62b15993adce93c

https://cloud.google.com/blog/ja/products/serverless/serverless-load-balancing-terraform-hard-way

https://inside.pixiv.blog/2020/05/08/161041

・GCS

https://blog.grasys.io/post/okawa/tf-gcs-logging/

https://qiita.com/y_hideshi/items/5c8a5d5d4c7a30278378

https://kawabatas.hatenablog.com/entry/2018/08/12/114150

https://www.javaer101.com/en/article/28846055.html

・GCP IPアドレス発行

https://www.namakedame.work/terraform-gcp-static-ip/

https://github.com/terraform-google-modules/terraform-google-lb-http/issues/112

・Shell

http://psychedelicnekopunch.com/archives/2055

https://qiita.com/simarei/items/f4809314399562309e4b

https://sechiro.hatenablog.com/entry/2015/01/25/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E4%B8%AD%E3%81%A7%E5%A4%89%E6%95%B0%E3%82%92%E5%88%86%E5%89%B2%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AB%E3%81%AF%E3%80%81cut

https://genzouw.com/entry/2019/12/17/120057/1831/

https://qiita.com/ponsuke0531/items/a56126b8b218f74faa10

http://kodama.fubuki.info/wiki/wiki.cgi/bash/tips?lang=jp

http://amano41.hatenablog.jp/entry/bash-supports-addition-assignment-operator

https://www.appmeshworkshop.com/cleanup/route53/

https://takuya-1st.hatenablog.jp/entry/2017/03/22/144700

https://linuxfan.info/cat-string-in-shell


完成してきちんと動くのはイイのですがドメインのレジストラがTerraformからの制御に対応していないとかAPIが準備されていないとかなところが多いので、

Route53の登録後にNSレコードをレジストラ側に設定してやる必要があります。後はGoogleのLBのドメインの反映が驚くほど時間がかかるので、コマンド打ってから構築完了までに楽勝で15分ほどかかります。とりあえずこの構成だと非常にバカ高いというところがあって、どうにかして安価で抑える方法を考えたいのですがいい手立て思いつきません。ご教示いただけると幸いです。

それとRoute53のAレコード削除とかをShellスクリプトからキックしていたりするのであまりいいパターンではありません。よい子は真似をしないようにですね。


◆GitHub

いちおう上げています。(2022-07-03 Terraform v1.2.2で動作確認済)

https://github.com/Otazoman/terraform_dupulicate_staticsite/tree/main/aws_gcp ※2024年9月に少し修正して最新のterraform(1.9.5)で動作確認しました。GitHub参照してください。

コメント

このブログの人気の投稿

GASでGoogleDriveのサブフォルダとファイル一覧を出力する

証券外務員1種勉強(計算式暗記用メモ)

マクロ経済学(IS-LM分析)