도메인 사용 정적 호스팅

아키텍처

도메인 사용 정적 호스팅은 웹사이트 제공을 위해 스토리지 버킷을 사용하여 간단한 사이트를 만드는 간단한 정적 웹사이트 제작 도구입니다.

  • 스토리지 - 파일 스토리지 - Cloud Storage
  • 네트워킹 - 부하 분산 - Cloud 부하 분산기
  • 네트워킹 - DNS - Cloud DNS
  • 도메인 관리 - 등록기관 - Cloud Domains

시작하기

Cloud Shell에서 소스 코드의 복사본에 대해 다음 링크를 클릭합니다. 그러면 단일 명령어로 프로젝트에서 애플리케이션의 작업 복사본이 작동합니다.

Cloud Shell에서 열기

GitHub에서 소스 코드 보기


도메인 사용 정적 호스팅 구성요소

도메인 사용 정적 호스팅 아키텍처에는 여러 제품이 사용됩니다. 다음은 관련 동영상 링크, 제품 문서 및 대화형 둘러보기를 포함하여 구성 요소에 대한 자세한 정보와 함께 구성요소 목록을 보여줍니다.
동영상 문서 둘러보기
Cloud Storage Cloud Storage는 파일 스토리지 및 http(s)를 통한 공개 이미지 제공을 지원합니다.
Cloud DNS Cloud DNS는 DNS 관리를 제공하여, Google의 기존 DNS 인프라를 사용하여 자체 DNS 인프라를 실행할 수 있게 해줍니다.
Cloud Load Balancing Google Cloud 부하 분산기를 사용하여 스토리지 버킷 앞에 부하 분산기를 배치하여 SSL 인증서, Logging, Monitoring을 사용할 수 있습니다.
Cloud Domains Cloud Domains를 사용하면 도메인 등록기관처럼 도메인을 구매할 수 있지만 이렇게 한 다음 Google Cloud 계정을 통해 도메인을 관리하고 결제를 처리할 수 있습니다.

스크립트

설치 스크립트는 go 및 Terraform CLI 도구로 작성된 실행 파일을 사용하여 빈 프로젝트를 가져오고 여기에 애플리케이션을 설치합니다. 출력은 작동하는 애플리케이션과 부하 분산 IP 주소의 URL입니다.

./main.tf

서비스 사용 설정

Google Cloud 서비스는 기본적으로 프로젝트에서 사용 중지되어 있습니다. 여기에서 솔루션을 사용하려면 다음을 활성화해야 합니다.

  • Cloud Domains - 도메인 등록
  • Cloud Storage - 정적 파일 호스팅
  • Cloud Compute - 부하 분산기에 대해 액세스를 제공합니다.
  • Cloud DNS - DNS 관리를 제공합니다.
variable "gcp_service_list" {
    description = "The list of apis necessary for the project"
    type        = list(string)
    default = [
        "domains.googleapis.com",
        "storage.googleapis.com",
        "compute.googleapis.com",
        "dns.googleapis.com",
        "appengine.googleapis.com",
    ]
}

resource "google_project_service" "all" {
    for_each                   = toset(var.gcp_service_list)
    project                    = var.project_number
    service                    = each.key
    disable_dependent_services = false
    disable_on_destroy         = false
}

DNS 영역 만들기

레코드를 관리하고, 부하 분산기에 연결하고, 도메인 등록 기관과 상호 작용하고, DNS 요청을 다른 DNS 서버에 제공할 수 있도록 DNS 영역을 만듭니다.

resource "google_dns_managed_zone" "dnszone" {
    project     = var.project_id
    name        = local.clouddnszone
    dns_name    = "${var.domain}."
    description = "A DNS Zone for managing ${var.domain}"
}

SSL 인증서 만들기

SSL 인증서는 https 프로토콜 요청에 필요합니다. 그러면 나중에 명령어에서 부하 분산기에 연결하는 인증서가 생성됩니다.

resource "google_compute_managed_ssl_certificate" "cert" {
    name        = "${local.basename}-cert"
    description = "Cert for ${local.basename}-microsite"
    project     = var.project_id

    managed {
        domains = [var.domain]
    }

    lifecycle {
        create_before_destroy = true
    }
}

외부 IP 주소 만들기

도메인 이름에 호스트를 바인딩하고 인터넷에서 일반적으로 통신을 수행하기 위해 필요합니다.

resource "google_compute_global_address" "ip" {
    project    = var.project_id
    name       = "${local.basename}-ip"
    ip_version = "IPV4"
}

스토리지 버킷 만들기

이렇게 하면 도메인 이름을 따라 버킷을 만든 후 웹 서버 역할을 수행하는 적절한 구성으로 버킷을 설정합니다. 마지막으로 사이트에 대해 간단한 템플릿을 복사합니다.

resource "google_storage_bucket" "http_bucket" {
    name     = local.bucket
    project  = var.project_id
    location = var.location
    
    website {
        main_page_suffix = "index.html"
        not_found_page   = "404.html"
    }
}

resource "google_storage_bucket_iam_binding" "policy" {
    bucket = google_storage_bucket.http_bucket.name
    role   = "roles/storage.objectViewer"
    members = [
      "allUsers",
    ]
    depends_on = [google_storage_bucket.http_bucket]
  }

resource "google_storage_bucket_object" "archive" {
    name   = "index.html"
    bucket = google_storage_bucket.http_bucket.name
    source = "code/${var.yesorno}/index.html"
    depends_on = [
        google_project_service.all,
        google_storage_bucket.http_bucket,
    ]
}

단독 부하 분산기

여기에서는 부하 분산기를 만들고 부하 분산기가 액세스를 제공할 콘텐츠의 소스로 스토리지 버킷을 설정합니다.

resource "google_compute_backend_bucket" "be" {
    project     = var.project_id
    name        = "${local.basename}-be"
    bucket_name = google_storage_bucket.http_bucket.name
    depends_on = [google_storage_bucket.http_bucket]
}

resource "google_compute_url_map" "lb" {
    project         = var.project_id
    name            = "${local.basename}-lb"
    default_service = google_compute_backend_bucket.be.id
    depends_on = [google_compute_backend_bucket.be]
}

HTTP 사용 설정

부하 분산기의 포트 80을 실행 중인 서비스에 연결하기 위해 필요한 네트워킹 규칙을 만듭니다.

resource "google_compute_target_http_proxy" "lb-proxy" {
    project = var.project_id
    name    = "${local.basename}-lb-proxy"
    url_map = google_compute_url_map.lb.id
    depends_on = [google_compute_url_map.lb]
}

resource "google_compute_forwarding_rule" "http-lb-forwarding-rule" {
    project               = var.project_id
    name                  = "${local.basename}-http-lb-forwarding-rule"
    provider              = google-beta
    region                = "none"
    load_balancing_scheme = "EXTERNAL"
    port_range            = "80"
    target                = google_compute_target_http_proxy.lb-proxy.id
    ip_address            = google_compute_global_address.ip.id
    depends_on = [google_compute_target_http_proxy.lb-proxy]
}

HTTPS 사용 설정

부하 분산기의 포트 443을 실행 중인 서비스에 연결하기 위해 필요한 네트워킹 규칙을 만듭니다. 또한 인증서를 올바르게 연결합니다.

resource "google_compute_target_https_proxy" "ssl-lb-proxy" {
    project          = var.project_id
    name             = "${local.basename}-ssl-lb-proxy"
    url_map          = google_compute_url_map.lb.id
    ssl_certificates = [google_compute_managed_ssl_certificate.cert.id]
    depends_on = [google_compute_url_map.lb,google_compute_managed_ssl_certificate.cert ]
}

resource "google_compute_forwarding_rule" "https-lb-forwarding-rule" {
    project               = var.project_id
    name                  = "${local.basename}-https-lb-forwarding-rule"
    provider              = google-beta
    region                = "none"
    load_balancing_scheme = "EXTERNAL"
    port_range            = "443"
    target                = google_compute_target_https_proxy.ssl-lb-proxy.id
    ip_address            = google_compute_global_address.ip.id
    depends_on = [google_compute_target_https_proxy.ssl-lb-proxy]
}

A 레코드 설정

Cloud DNS에서 도메인 이름이 생성된 IP 주소로 확인되게 만드는 적절한 규칙을 만듭니다.

resource "google_dns_record_set" "a" {
    project      = var.project_id
    name         = "${var.domain}."
    managed_zone = google_dns_managed_zone.dnszone.name
    type         = "A"
    ttl          = 60


    rrdatas = [google_compute_global_address.ip.address]
    depends_on = [google_compute_global_address.ip]
}

./deploystack.go

도메인 가용성 쿼리

요청 중인 도메인의 가용성에 대한 정보를 가져옵니다.

func domainsSearch(project, domain string) ([]*domainspb.RegisterParameters, error) {
    ctx := context.Background()

    c, err := domains.NewClient(ctx)
    if err != nil {
        return nil, err
    }
    defer c.Close()

    req := &domainspb.SearchDomainsRequest{
        Query:    domain,
        Location: fmt.Sprintf("projects/%s/locations/global", project),
    }
    resp, err := c.SearchDomains(ctx, req)
    if err != nil {
        return nil, err
    }

    return resp.RegisterParameters, nil
}

func domainIsAvailable(project, domain string) (*domainspb.RegisterParameters, error) {
    list, err := domainsSearch(project, domain)
    if err != nil {
        return nil, err
    }
    for _, v := range list {
        if v.DomainName == domain {
            return v, err
        }
    }

    return nil, err
}

도메인 소유권 쿼리

요청된 도메인을 현재 사용자가 소유하는지 여부에 대한 정보를 가져옵니다.

func domainsIsVerified(project, domain string) (bool, error) {
    ctx := context.Background()

    c, err := domains.NewClient(ctx)
    if err != nil {
        return false, err
    }
    defer c.Close()

    req := &domainspb.ListRegistrationsRequest{
        Filter: fmt.Sprintf("domainName=\"%s\"", domain),
        Parent: fmt.Sprintf("projects/%s/locations/global", project),
    }
    it := c.ListRegistrations(ctx, req)
    for {
        resp, err := it.Next()
        if err == iterator.Done {
            break
        }
        if err != nil {
            return false, err
        }

        if resp.DomainName == domain {
            return true, nil
        }
    }

    return false, nil
}

Cloud Domains로 도메인 등록

실제로 등록기관에서 도메인을 구입하는 작업을 수행합니다.

func domainRegister(project string, domaininfo *domainspb.RegisterParameters, contact ContactData) error {
    ctx := context.Background()

    parent := fmt.Sprintf("projects/%s/locations/global", project)

    c, err := domains.NewClient(ctx)
    if err != nil {
        return err
    }
    defer c.Close()

    dnscontact, err := contact.DomainContact()
    if err != nil {
        return err
    }

    req := &domainspb.RegisterDomainRequest{
        Registration: &domainspb.Registration{
            Name:       fmt.Sprintf("%s/registrations/%s", parent, domaininfo.DomainName),
            DomainName: domaininfo.DomainName,
            DnsSettings: &domainspb.DnsSettings{
                DnsProvider: &domainspb.DnsSettings_CustomDns_{
                    CustomDns: &domainspb.DnsSettings_CustomDns{
                        NameServers: []string{
                            "ns-cloud-e1.googledomains.com",
                            "ns-cloud-e2.googledomains.com",
                            "ns-cloud-e3.googledomains.com",
                            "ns-cloud-e4.googledomains.com",
                        },
                    },
                },
            },
            ContactSettings: &dnscontact,
        },
        Parent:      parent,
        YearlyPrice: domaininfo.YearlyPrice,
    }

    if _, err := c.RegisterDomain(ctx, req); err != nil {
        return err
    }

    return nil
}


결론

이제 도메인 및 보안 정적 사이트를 사용하여 작은 웹사이트가 설정되었습니다. 또한 환경에 맞게 이 솔루션을 수정하거나 확장할 수 있도록 모든 코드가 준비되었습니다.