Overview
- Static FileServer Application as a web server container
- Cloud Run is used as the static page hosting service
- Cloud DNS is to offer DNS records for the Search Console
Prerequisites
- Prepare the Domain to publish static pages.
How to Build the Static Page Site
1. Enable API
To use the Cloud DNS service and manage the resource by the Tofu or Terraform or something, a developer need to enable the Cloud DNS service.
1// Variable for GCP
2variable "gcp_project" {
3 type = object({
4 project = string // The gcp project name
5 region = string // The region of gcp project
6 })
7}
8
9variable "gcp_services" {
10 type = list(string) // Enabling the service name of api
11}
12
13// Enable APIs
14resource "google_project_service" "api_enable" {
15 for_each = toset(var.gcp_services)
16 project = var.gcp_project.project
17 service = each.value
18}
2. Prepare the static pages container
In this part, to build simple file http server, this part share golang example code. You can choose any language or a web server container for this.
1package main
2
3import (
4 "embed"
5 "flag"
6 "fmt"
7 "io/fs"
8 "log"
9 "net/http"
10 "os"
11)
12
13//go:embed pub
14var staticFs embed.FS
15
16func fileHandler() http.HandlerFunc {
17 pub, _ := fs.Sub(staticFs, "pub")
18 handler := http.FileServerFS(pub)
19
20 return func(w http.ResponseWriter, req *http.Request) {
21 w.Header().Set("Cache-Control", "public, max-age=3600")
22 handler.ServeHTTP(w, req)
23 }
24}
25
26func main() {
27 portNum := flag.String("p", "8080", "server port")
28 host := flag.String("i", "", "interface name")
29 flag.Parse()
30
31 portEnv := os.Getenv("PORT")
32 if len(portEnv) > 0 {
33 *portNum = portEnv
34 }
35
36 srv := fmt.Sprintf("%s:%s", *host, *portNum)
37 log.Printf("server: %s", srv)
38
39 handler := fileHandler()
40 http.Handle("/", handler)
41
42 if err := http.ListenAndServe(srv, nil); err != nil {
43 log.Fatal(err)
44 }
45}
The below command is the bhild check on local
1go build -o chk ./cmd/main.go
3. Set up the Artifact Registry
To run the file server on Cloud Run, the Artifact Registry is required. to store the file server container, this part builds the Artifact Registry.
1variable "run" {
2 type = object({
3 name = string
4 project = string
5 location = string
6 image = string
7 args = list(string)
8 port = optional(number, 8080)
9 allusers = optional(bool, true)
10 ingress = optional(string, "INGRESS_TRAFFIC_ALL")
11 instance = optional(
12 object({ min = number, max = number, cpu = string, mem = string }),
13 { min = 0, max = 2, cpu = "1", mem = "512Mi" }
14 )
15
16 domain = optional(object({
17 name = string
18 namespace = string
19 }))
20
21 // AR
22 tags = optional(list(string), ["v", "latest"])
23 retation_period = optional(string, "86400s")
24 })
25}
1locals {
2 repo_name = "${var.run.name}-repo"
3 ar_url = "${var.run.location}-docker.pkg.dev/${var.run.project}/${local.repo_name}/${var.run.image}"
4}
5
6resource "google_artifact_registry_repository" "repo" {
7 location = var.run.location
8 repository_id = "${var.run.name}-repo"
9 description = "The repository for the Cloud Run Services"
10 format = "DOCKER"
11
12
13 cleanup_policies {
14 id = "keep-tagged-version"
15 action = "KEEP"
16 condition {
17 tag_state = "TAGGED"
18 tag_prefixes = var.run.tags
19 }
20 }
21 cleanup_policies {
22 id = "delete-any"
23 action = "DELETE"
24 condition {
25 tag_state = "ANY"
26 older_than = var.run.retation_period // 1 day
27 }
28 }
29}
4. Build the Cloud Run
To run the file server container, this part builds the Cloud Run Service.
1resource "google_cloud_run_v2_service" "app" {
2 depends_on = [google_artifact_registry_repository.repo]
3
4 name = var.run.name
5 location = var.run.location
6 ingress = var.run.ingress
7 template {
8 containers {
9 name = var.run.name
10 ports {
11 container_port = var.run.port
12 }
13 image = "${local.ar_url}:latest"
14
15 resources {
16 limits = {
17 memory = var.run.instance.mem
18 cpu = var.run.instance.cpu
19 }
20 }
21 }
22
23 scaling {
24 min_instance_count = var.run.instance.min
25 max_instance_count = var.run.instance.max
26 }
27 }
28
29 lifecycle {
30 ignore_changes = [
31 template[0].containers[0].image
32 ]
33 }
34}
35
36resource "google_cloud_run_v2_service_iam_member" "app" {
37 depends_on = [google_cloud_run_v2_service.app]
38 count = var.run.allusers ? 1 : 0
39 location = google_cloud_run_v2_service.app.location
40 name = google_cloud_run_v2_service.app.name
41 role = "roles/run.invoker"
42 member = "allUsers"
43}
5. Set up a Custom domain
To allow domain name access, this part set up custom domain configuration.
1variable "domain" {
2 type = object({
3 zone_name = string
4 dns_name = string
5 description = string
6 search_console_code = string
7 })
8}
9
10resource "google_dns_managed_zone" "content" {
11 project = var.gcp_project.project
12 name = var.domain.zone_name
13 dns_name = var.domain.dns_name
14 description = var.domain.descriotpion
15 labels = {
16 domain = "media"
17 }
18}
19
20resource "google_dns_record_set" "gcs_hosting_txt" {
21 name = var.domain.dns_name
22 type = "TXT"
23 ttl = 600
24 managed_zone = google_dns_managed_zone.content.name
25 rrdatas = [var.domain.search_console_code]
26}
27
28resource "google_dns_record_set" "run_pages" {
29 name = var.domain.dns_name
30 type = "CNAME"
31 ttl = 300
32 managed_zone = google_dns_managed_zone.content.name
33 rrdatas = ["ghs.googlehosted.com."]
34}
1resource "google_cloud_run_domain_mapping" "custom" {
2 depends_on = [google_cloud_run_v2_service.app]
3 for_each = var.run.domain == null ? {} : { "dns" : var.run.domain }
4 name = each.value.name
5 location = var.run.location
6 metadata {
7 namespace = each.value.namespace
8 }
9 spec {
10 route_name = google_cloud_run_v2_service.app.name
11 }
12}