[{"data":1,"prerenderedAt":1762},["ShallowReactive",2],{"navigation":3,"projects-page":34,"projects":52},[4],{"title":5,"path":6,"stem":7,"children":8,"page":33},"Articles","\u002Farticles","articles",[9,13,17,21,25,29],{"title":10,"path":11,"stem":12},"用 Daily Snapshot 提升統計查詢速度","\u002Farticles\u002Fdaily-snapshot","articles\u002Fdaily-snapshot",{"title":14,"path":15,"stem":16},"GKE 部署","\u002Farticles\u002Fgke-deployment","articles\u002Fgke-deployment",{"title":18,"path":19,"stem":20},"從 LLM 到 Agent：打通底層邏輯","\u002Farticles\u002Fllm-to-agent","articles\u002Fllm-to-agent",{"title":22,"path":23,"stem":24},"資訊安全實踐","\u002Farticles\u002Fsecurity-best-practices","articles\u002Fsecurity-best-practices",{"title":26,"path":27,"stem":28},"單機架構的性能優化","\u002Farticles\u002Fsingle-machine-performance","articles\u002Fsingle-machine-performance",{"title":30,"path":31,"stem":32},"伺服器渲染 SSR","\u002Farticles\u002Fssr","articles\u002Fssr",false,{"id":35,"title":36,"body":37,"description":38,"extension":39,"links":40,"meta":46,"navigation":47,"path":48,"seo":49,"stem":50,"__hash__":51},"pages\u002Fprojects.yml","最新專案",null,"這裡記錄了一些我做過的專案，每個專案都讓我在系統設計上學到一些新東西。","yml",[41,44],{"label":42,"color":43},"工作委託","neutral",{"label":45},"寄信給我",{},true,"\u002Fprojects",{"title":36,"description":38},"projects","R2yxa-GR5kh0g3m-uzirjAq8QF7NMbF-KmXP_3J8Pss",[53,692,1035,1397],{"id":54,"title":55,"author":56,"body":60,"date":676,"demoNote":37,"demoUrl":37,"description":677,"extension":678,"image":679,"meta":680,"navigation":47,"path":681,"seo":682,"stem":683,"tags":684,"url":690,"__hash__":691},"projectPages\u002Fprojects\u002Fgshop-api.md","gshop-api",{"name":57,"avatar":58},"Gary",{"src":59,"alt":57},"\u002Fimages\u002Fselfie.webp",{"type":61,"value":62,"toc":666},"minimark",[63,67,71,106,109,120,123,129,132,137,147,157,163,169,172,299,302,306,311,324,337,421,424,445,447,451,459,464,468,515,519,522,659,662],[64,65,66],"h2",{"id":66},"架構概覽",[68,69,70],"p",{},"gshop-api 是整個電商系統的後端核心，負責處理商品、訂單、會員認證等 API 請求。",[72,73,74,82,88,94,100],"ul",{},[75,76,77,81],"li",{},[78,79,80],"strong",{},"Runtime","：Node.js \u002F Express",[75,83,84,87],{},[78,85,86],{},"Port","：3001",[75,89,90,93],{},[78,91,92],{},"資料庫","：Supabase PostgreSQL（透過 Session Pooler 連線）",[75,95,96,99],{},[78,97,98],{},"部署環境","：GKE Autopilot，asia-east1",[75,101,102,105],{},[78,103,104],{},"Image Registry","：Google Artifact Registry",[64,107,108],{"id":108},"部署架構",[110,111,116],"pre",{"className":112,"code":114,"language":115},[113],"language-text","GitHub (main) → GitHub Actions → docker build → Artifact Registry\n                                                      ↓\n                                              GKE pull image\n                                                      ↓\n                                         kubectl rollout restart\n","text",[117,118,114],"code",{"__ignoreMap":119},"",[68,121,122],{},"流量路徑：",[110,124,127],{"className":125,"code":126,"language":115},[113],"Cloudflare → GCP Load Balancer → Ingress → gshop-api Pod\n",[117,128,126],{"__ignoreMap":119},[64,130,131],{"id":131},"遇到的問題與解法",[133,134,136],"h3",{"id":135},"_1-supabase-連線失敗enotfound","1. Supabase 連線失敗（ENOTFOUND）",[68,138,139,142,143,146],{},[78,140,141],{},"問題","：部署後 API 無法連線到 Supabase，log 顯示 ",[117,144,145],{},"ENOTFOUND","。",[68,148,149,152,153,156],{},[78,150,151],{},"原因","：Supabase 直連位址 ",[117,154,155],{},"db.xxx.supabase.co:5432"," 僅支援 IPv6，而 GKE Autopilot 的節點只有 IPv4。本地 Mac 可以直連是因為 Mac 同時支援 IPv4 與 IPv6。",[68,158,159,162],{},[78,160,161],{},"解法","：改用 Supabase Session Pooler connection string，走 IPv4：",[110,164,167],{"className":165,"code":166,"language":115},[113],"postgresql:\u002F\u002Fpostgres.xxx:PASSWORD@aws-1-ap-southeast-1.pooler.supabase.com:5432\u002Fpostgres\n",[117,168,166],{"__ignoreMap":119},[68,170,171],{},"更新 K8s Secret：",[110,173,177],{"className":174,"code":175,"language":176,"meta":119,"style":119},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","NEW_URL=\"postgresql:\u002F\u002F...\"\nkubectl patch secret gshop-api-secret \\\n  -p \"{\\\"data\\\":{\\\"DATABASE_URL\\\":\\\"$(echo -n $NEW_URL | base64)\\\"}}\"\nkubectl rollout restart deployment\u002Fgshop-api\n","bash",[117,178,179,202,221,285],{"__ignoreMap":119},[180,181,184,188,192,195,199],"span",{"class":182,"line":183},"line",1,[180,185,187],{"class":186},"sTEyZ","NEW_URL",[180,189,191],{"class":190},"sMK4o","=",[180,193,194],{"class":190},"\"",[180,196,198],{"class":197},"sfazB","postgresql:\u002F\u002F...",[180,200,201],{"class":190},"\"\n",[180,203,205,209,212,215,218],{"class":182,"line":204},2,[180,206,208],{"class":207},"sBMFI","kubectl",[180,210,211],{"class":197}," patch",[180,213,214],{"class":197}," secret",[180,216,217],{"class":197}," gshop-api-secret",[180,219,220],{"class":186}," \\\n",[180,222,224,227,230,233,236,239,241,244,246,249,251,254,256,259,263,266,269,272,275,278,280,283],{"class":182,"line":223},3,[180,225,226],{"class":197},"  -p",[180,228,229],{"class":190}," \"",[180,231,232],{"class":197},"{",[180,234,235],{"class":186},"\\\"",[180,237,238],{"class":197},"data",[180,240,235],{"class":186},[180,242,243],{"class":197},":{",[180,245,235],{"class":186},[180,247,248],{"class":197},"DATABASE_URL",[180,250,235],{"class":186},[180,252,253],{"class":197},":",[180,255,235],{"class":186},[180,257,258],{"class":190},"$(",[180,260,262],{"class":261},"s2Zo4","echo",[180,264,265],{"class":197}," -n ",[180,267,268],{"class":186},"$NEW_URL",[180,270,271],{"class":190}," |",[180,273,274],{"class":207}," base64",[180,276,277],{"class":190},")",[180,279,235],{"class":186},[180,281,282],{"class":197},"}}",[180,284,201],{"class":190},[180,286,288,290,293,296],{"class":182,"line":287},4,[180,289,208],{"class":207},[180,291,292],{"class":197}," rollout",[180,294,295],{"class":197}," restart",[180,297,298],{"class":197}," deployment\u002Fgshop-api\n",[300,301],"hr",{},[133,303,305],{"id":304},"_2-502-bad-gateway-健康檢查失敗","2. 502 Bad Gateway — 健康檢查失敗",[68,307,308,310],{},[78,309,141],{},"：服務部署完成，但 GCP Load Balancer 回傳 502。",[68,312,313,315,316,319,320,323],{},[78,314,151],{},"：GCP Load Balancer 預設對 ",[117,317,318],{},"GET \u002F"," 做健康檢查，但 gshop-api 沒有 ",[117,321,322],{},"\u002F"," 路由，回傳非 200，LB 判定為不健康，停止轉發流量。",[68,325,326,328,329,332,333,336],{},[78,327,161],{},"：建立 ",[117,330,331],{},"BackendConfig","，將健康檢查路徑改為 ",[117,334,335],{},"\u002Fhealth","：",[110,338,342],{"className":339,"code":340,"language":341,"meta":119,"style":119},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","apiVersion: cloud.google.com\u002Fv1\nkind: BackendConfig\nmetadata:\n  name: gshop-api-backend-config\nspec:\n  healthCheck:\n    requestPath: \u002Fhealth\n    type: HTTP\n","yaml",[117,343,344,355,365,373,383,391,399,410],{"__ignoreMap":119},[180,345,346,350,352],{"class":182,"line":183},[180,347,349],{"class":348},"swJcz","apiVersion",[180,351,253],{"class":190},[180,353,354],{"class":197}," cloud.google.com\u002Fv1\n",[180,356,357,360,362],{"class":182,"line":204},[180,358,359],{"class":348},"kind",[180,361,253],{"class":190},[180,363,364],{"class":197}," BackendConfig\n",[180,366,367,370],{"class":182,"line":223},[180,368,369],{"class":348},"metadata",[180,371,372],{"class":190},":\n",[180,374,375,378,380],{"class":182,"line":287},[180,376,377],{"class":348},"  name",[180,379,253],{"class":190},[180,381,382],{"class":197}," gshop-api-backend-config\n",[180,384,386,389],{"class":182,"line":385},5,[180,387,388],{"class":348},"spec",[180,390,372],{"class":190},[180,392,394,397],{"class":182,"line":393},6,[180,395,396],{"class":348},"  healthCheck",[180,398,372],{"class":190},[180,400,402,405,407],{"class":182,"line":401},7,[180,403,404],{"class":348},"    requestPath",[180,406,253],{"class":190},[180,408,409],{"class":197}," \u002Fhealth\n",[180,411,413,416,418],{"class":182,"line":412},8,[180,414,415],{"class":348},"    type",[180,417,253],{"class":190},[180,419,420],{"class":197}," HTTP\n",[68,422,423],{},"並在 Service 加上 annotation：",[110,425,427],{"className":339,"code":426,"language":341,"meta":119,"style":119},"cloud.google.com\u002Fbackend-config: '{\"default\": \"gshop-api-backend-config\"}'\n",[117,428,429],{"__ignoreMap":119},[180,430,431,434,436,439,442],{"class":182,"line":183},[180,432,433],{"class":348},"cloud.google.com\u002Fbackend-config",[180,435,253],{"class":190},[180,437,438],{"class":190}," '",[180,440,441],{"class":197},"{\"default\": \"gshop-api-backend-config\"}",[180,443,444],{"class":190},"'\n",[300,446],{},[133,448,450],{"id":449},"_3-imagepullbackoff-gke-無法拉取-image","3. ImagePullBackOff — GKE 無法拉取 Image",[68,452,453,455,456,146],{},[78,454,141],{},"：Pod 啟動失敗，狀態顯示 ",[117,457,458],{},"ImagePullBackOff",[68,460,461,463],{},[78,462,151],{},"：GKE 的 Service Account 沒有 Artifact Registry 的讀取權限。",[68,465,466,336],{},[78,467,161],{},[110,469,471],{"className":174,"code":470,"language":176,"meta":119,"style":119},"gcloud projects add-iam-policy-binding gshop-497319 \\\n  --member=\"serviceAccount:620172615694-compute@developer.gserviceaccount.com\" \\\n  --role=\"roles\u002Fartifactregistry.reader\"\n",[117,472,473,489,503],{"__ignoreMap":119},[180,474,475,478,481,484,487],{"class":182,"line":183},[180,476,477],{"class":207},"gcloud",[180,479,480],{"class":197}," projects",[180,482,483],{"class":197}," add-iam-policy-binding",[180,485,486],{"class":197}," gshop-497319",[180,488,220],{"class":186},[180,490,491,494,496,499,501],{"class":182,"line":204},[180,492,493],{"class":197},"  --member=",[180,495,194],{"class":190},[180,497,498],{"class":197},"serviceAccount:620172615694-compute@developer.gserviceaccount.com",[180,500,194],{"class":190},[180,502,220],{"class":186},[180,504,505,508,510,513],{"class":182,"line":223},[180,506,507],{"class":197},"  --role=",[180,509,194],{"class":190},[180,511,512],{"class":197},"roles\u002Fartifactregistry.reader",[180,514,201],{"class":190},[64,516,518],{"id":517},"cicd-設定","CI\u002FCD 設定",[68,520,521],{},"每次 push 到 main，GitHub Actions 自動執行：",[110,523,525],{"className":339,"code":524,"language":341,"meta":119,"style":119},"- uses: google-github-actions\u002Fauth@v2\n  with:\n    credentials_json: ${{ secrets.GCP_SA_KEY }}\n\n- name: Build and push image\n  run: |\n    docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fapi:latest .\n    docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fapi:latest\n\n- uses: google-github-actions\u002Fget-gke-credentials@v2\n  with:\n    cluster_name: gshop-cluster\n    location: asia-east1\n\n- run: kubectl rollout restart deployment\u002Fgshop-api\n",[117,526,527,540,547,557,562,574,585,590,595,600,612,619,630,641,646],{"__ignoreMap":119},[180,528,529,532,535,537],{"class":182,"line":183},[180,530,531],{"class":190},"-",[180,533,534],{"class":348}," uses",[180,536,253],{"class":190},[180,538,539],{"class":197}," google-github-actions\u002Fauth@v2\n",[180,541,542,545],{"class":182,"line":204},[180,543,544],{"class":348},"  with",[180,546,372],{"class":190},[180,548,549,552,554],{"class":182,"line":223},[180,550,551],{"class":348},"    credentials_json",[180,553,253],{"class":190},[180,555,556],{"class":197}," ${{ secrets.GCP_SA_KEY }}\n",[180,558,559],{"class":182,"line":287},[180,560,561],{"emptyLinePlaceholder":47},"\n",[180,563,564,566,569,571],{"class":182,"line":385},[180,565,531],{"class":190},[180,567,568],{"class":348}," name",[180,570,253],{"class":190},[180,572,573],{"class":197}," Build and push image\n",[180,575,576,579,581],{"class":182,"line":393},[180,577,578],{"class":348},"  run",[180,580,253],{"class":190},[180,582,584],{"class":583},"s7zQu"," |\n",[180,586,587],{"class":182,"line":401},[180,588,589],{"class":197},"    docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fapi:latest .\n",[180,591,592],{"class":182,"line":412},[180,593,594],{"class":197},"    docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fapi:latest\n",[180,596,598],{"class":182,"line":597},9,[180,599,561],{"emptyLinePlaceholder":47},[180,601,603,605,607,609],{"class":182,"line":602},10,[180,604,531],{"class":190},[180,606,534],{"class":348},[180,608,253],{"class":190},[180,610,611],{"class":197}," google-github-actions\u002Fget-gke-credentials@v2\n",[180,613,615,617],{"class":182,"line":614},11,[180,616,544],{"class":348},[180,618,372],{"class":190},[180,620,622,625,627],{"class":182,"line":621},12,[180,623,624],{"class":348},"    cluster_name",[180,626,253],{"class":190},[180,628,629],{"class":197}," gshop-cluster\n",[180,631,633,636,638],{"class":182,"line":632},13,[180,634,635],{"class":348},"    location",[180,637,253],{"class":190},[180,639,640],{"class":197}," asia-east1\n",[180,642,644],{"class":182,"line":643},14,[180,645,561],{"emptyLinePlaceholder":47},[180,647,649,651,654,656],{"class":182,"line":648},15,[180,650,531],{"class":190},[180,652,653],{"class":348}," run",[180,655,253],{"class":190},[180,657,658],{"class":197}," kubectl rollout restart deployment\u002Fgshop-api\n",[68,660,661],{},"GitHub Actions runner 是 ubuntu amd64，build 出的 image 天生就是 amd64，不需要 Cloud Build。",[663,664,665],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":119,"searchDepth":204,"depth":204,"links":667},[668,669,670,675],{"id":66,"depth":204,"text":66},{"id":108,"depth":204,"text":108},{"id":131,"depth":204,"text":131,"children":671},[672,673,674],{"id":135,"depth":223,"text":136},{"id":304,"depth":223,"text":305},{"id":449,"depth":223,"text":450},{"id":517,"depth":204,"text":518},"2026-06-15","電商系統的後端核心，負責商品、訂單與會員 API，串接 Supabase PostgreSQL 與 Google Cloud Storage。","md","\u002Fprojects\u002Fapi.jpg",{},"\u002Fprojects\u002Fgshop-api",{"title":55,"description":677},"projects\u002Fgshop-api",[685,686,687,688,689],"Node.js","GKE","Kubernetes","CI\u002FCD","PostgreSQL","https:\u002F\u002Fgithub.com\u002Forgs\u002Fvery-cool-gshop\u002Frepositories","XHnhJxQUFZQ-fmWrVoAZ073dMjMGQAjOSDLowfnrGIE",{"id":693,"title":694,"author":695,"body":697,"date":1023,"demoNote":37,"demoUrl":37,"description":1024,"extension":678,"image":1025,"meta":1026,"navigation":47,"path":1027,"seo":1028,"stem":1029,"tags":1030,"url":690,"__hash__":1034},"projectPages\u002Fprojects\u002Fgshop-architecture.md","GShop 系統架構",{"name":57,"avatar":696},{"src":59,"alt":57},{"type":61,"value":698,"toc":1008},[699,706,709,712,766,769,771,775,782,788,795,797,801,804,810,815,826,835,837,841,851,855,861,864,868,871,905,908,912,915,946,949,951,954,958,965,971,981,985,991,993,996,999,1005],[68,700,701],{},[702,703],"img",{"alt":704,"src":705},"GShop 系統架構圖","\u002Fimages\u002Fstructure.png",[64,707,708],{"id":708},"系統概覽",[68,710,711],{},"GShop 是一套電商系統，由三個獨立服務組成，統一部署在 GKE Autopilot 上：",[713,714,715,730],"table",{},[716,717,718],"thead",{},[719,720,721,725,728],"tr",{},[722,723,724],"th",{},"服務",[722,726,727],{},"角色",[722,729,86],{},[731,732,733,744,755],"tbody",{},[719,734,735,738,741],{},[736,737,55],"td",{},[736,739,740],{},"Node.js REST API",[736,742,743],{},"3001",[719,745,746,749,752],{},[736,747,748],{},"gshop-dashboard",[736,750,751],{},"Nuxt.js 管理後台",[736,753,754],{},"3002",[719,756,757,760,763],{},[736,758,759],{},"gshop-web",[736,761,762],{},"Nuxt.js 前台購物網站",[736,764,765],{},"3003",[68,767,768],{},"三個服務共用同一個 GKE Cluster、同一個 Cloud Load Balancer IP，由 Ingress 依 Host header 分流。",[300,770],{},[64,772,774],{"id":773},"cicd-pipeline","CI\u002FCD Pipeline",[68,776,777,778,781],{},"三個 repo 分別位於 GitHub Org ",[117,779,780],{},"very-cool-gshop","，各自有獨立的 GitHub Actions workflow。",[110,783,786],{"className":784,"code":785,"language":115},[113],"push main → GitHub Actions\n              ↓\n         docker build (ubuntu-latest \u002F amd64)\n              ↓\n         push → Artifact Registry\n              ↓\n         kubectl rollout restart\n",[117,787,785],{"__ignoreMap":119},[68,789,790,791,794],{},"GitHub Actions runner 是 ",[117,792,793],{},"ubuntu-latest","，天生就是 amd64，build 出的 image 直接相容 GKE，不需要 Cloud Build。",[300,796],{},[64,798,800],{"id":799},"user-edge-層","User & Edge 層",[68,802,803],{},"使用者請求先經過 Cloudflare，再進入 GCP：",[110,805,808],{"className":806,"code":807,"language":115},[113],"User Browser\n     ↓ HTTPS\n  Cloudflare\n  DNS + Proxy（Full Strict）\n     ↓ HTTPS\nGCP Cloud Load Balancer\n",[117,809,807],{"__ignoreMap":119},[68,811,812],{},[78,813,814],{},"Cloudflare 的作用：",[72,816,817,820,823],{},[75,818,819],{},"DNS 解析 — 對外暴露 Cloudflare IP，不直接暴露 GCP 的真實 IP",[75,821,822],{},"Proxy — 所有流量過 Cloudflare 節點，提供 DDoS 防護與快取",[75,824,825],{},"TLS 終止 — 瀏覽器到 Cloudflare 這段的 HTTPS 由 Cloudflare 處理",[68,827,828,831,834],{},[78,829,830],{},"Full (Strict) 模式：",[832,833],"br",{},"\nCloudflare 到 GCP 這段也必須有合法憑證。使用 Cloudflare Origin Certificate，存成 K8s TLS Secret，由 Ingress 掛載使用。",[300,836],{},[64,838,840],{"id":839},"gke-autopilot-叢集","GKE Autopilot 叢集",[68,842,843,844,847,848,146],{},"叢集名稱 ",[117,845,846],{},"gshop-cluster","，部署在 ",[117,849,850],{},"asia-east1",[133,852,854],{"id":853},"ingress-host-based-路由","Ingress — host-based 路由",[110,856,859],{"className":857,"code":858,"language":115},[113],"api.garydemo.com      →  gshop-api:3001\ndashboard.garydemo.com →  gshop-dashboard:3002\nweb.garydemo.com      →  gshop-web:3003\n",[117,860,858],{"__ignoreMap":119},[68,862,863],{},"Cloud Load Balancer（IP：34.160.168.110）接收所有流量，Ingress Controller 根據 Host header 決定轉發目標。",[133,865,867],{"id":866},"tls-secret","TLS Secret",[68,869,870],{},"Cloudflare Origin Certificate 存成 K8s TLS Secret：",[110,872,874],{"className":174,"code":873,"language":176,"meta":119,"style":119},"kubectl create secret tls cloudflare-origin-cert \\\n  --cert=certificate.pem \\\n  --key=private.key\n",[117,875,876,893,900],{"__ignoreMap":119},[180,877,878,880,883,885,888,891],{"class":182,"line":183},[180,879,208],{"class":207},[180,881,882],{"class":197}," create",[180,884,214],{"class":197},[180,886,887],{"class":197}," tls",[180,889,890],{"class":197}," cloudflare-origin-cert",[180,892,220],{"class":186},[180,894,895,898],{"class":182,"line":204},[180,896,897],{"class":197},"  --cert=certificate.pem",[180,899,220],{"class":186},[180,901,902],{"class":182,"line":223},[180,903,904],{"class":197},"  --key=private.key\n",[68,906,907],{},"Ingress 掛載這個 Secret，讓 Cloudflare → GCP 這段走 HTTPS。",[133,909,911],{"id":910},"db-secret","DB Secret",[68,913,914],{},"資料庫連線字串存成 K8s Generic Secret：",[110,916,918],{"className":174,"code":917,"language":176,"meta":119,"style":119},"kubectl create secret generic gshop-api-secret \\\n  --from-literal=DATABASE_URL=\"postgresql:\u002F\u002F...\"\n",[117,919,920,935],{"__ignoreMap":119},[180,921,922,924,926,928,931,933],{"class":182,"line":183},[180,923,208],{"class":207},[180,925,882],{"class":197},[180,927,214],{"class":197},[180,929,930],{"class":197}," generic",[180,932,217],{"class":197},[180,934,220],{"class":186},[180,936,937,940,942,944],{"class":182,"line":204},[180,938,939],{"class":197},"  --from-literal=DATABASE_URL=",[180,941,194],{"class":190},[180,943,198],{"class":197},[180,945,201],{"class":190},[68,947,948],{},"gshop-api Pod 透過環境變數讀取，不會出現在 image 或 code 裡。",[300,950],{},[64,952,953],{"id":953},"外部服務",[133,955,957],{"id":956},"supabase-postgresql","Supabase PostgreSQL",[68,959,960,961,964],{},"gshop-api 透過 ",[78,962,963],{},"Session Pooler"," 連線，走 IPv4：",[110,966,969],{"className":967,"code":968,"language":115},[113],"aws-1-ap-southeast-1.pooler.supabase.com:5432\n",[117,970,968],{"__ignoreMap":119},[972,973,974],"blockquote",{},[68,975,976,977,980],{},"GKE Autopilot 節點只有 IPv4，Supabase 直連（",[117,978,979],{},"db.xxx.supabase.co","）是 IPv6 only，直接連會 ENOTFOUND。",[133,982,984],{"id":983},"google-cloud-storage","Google Cloud Storage",[68,986,987,988,146],{},"gshop-api 負責商品圖片的上傳與讀取，存放在 GCS Bucket ",[117,989,990],{},"gshop-files",[300,992],{},[64,994,995],{"id":995},"部署架構圖",[68,997,998],{},"整體流量與部署流向：",[110,1000,1003],{"className":1001,"code":1002,"language":115},[113],"GitHub push main\n       ↓\n  GitHub Actions\n  docker build → Artifact Registry\n       ↓\n  kubectl rollout restart\n       ↓\n  GKE Pod 拉新 image 滾動更新\n\nUser Browser → Cloudflare → Cloud LB (34.160.168.110)\n                                    ↓\n                             Ingress（host routing）\n                            ↙        ↓        ↘\n                      gshop-api  gshop-dashboard  gshop-web\n                           ↓             ↓\n                       Supabase       GCS Bucket\n                      PostgreSQL\n",[117,1004,1002],{"__ignoreMap":119},[663,1006,1007],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}",{"title":119,"searchDepth":204,"depth":204,"links":1009},[1010,1011,1012,1013,1018,1022],{"id":708,"depth":204,"text":708},{"id":773,"depth":204,"text":774},{"id":799,"depth":204,"text":800},{"id":839,"depth":204,"text":840,"children":1014},[1015,1016,1017],{"id":853,"depth":223,"text":854},{"id":866,"depth":223,"text":867},{"id":910,"depth":223,"text":911},{"id":953,"depth":204,"text":953,"children":1019},[1020,1021],{"id":956,"depth":223,"text":957},{"id":983,"depth":223,"text":984},{"id":995,"depth":204,"text":995},"2026-06-20","三服務電商系統的完整部署藍圖，從 CI\u002FCD 到 GKE 叢集、Cloudflare Proxy 到 Supabase 的全端架構設計。","\u002Fimages\u002Farchitecture.jpg",{},"\u002Fprojects\u002Fgshop-architecture",{"title":694,"description":1024},"projects\u002Fgshop-architecture",[686,687,1031,688,1032,1033],"Cloudflare","Supabase","GCS","y4KXw9ivzhVpoxYNezynOrpqVnB3byV5xZlc2eruhsQ",{"id":1036,"title":748,"author":1037,"body":1039,"date":676,"demoNote":1384,"demoUrl":1385,"description":1386,"extension":678,"image":1387,"meta":1388,"navigation":47,"path":1389,"seo":1390,"stem":1391,"tags":1392,"url":1395,"__hash__":1396},"projectPages\u002Fprojects\u002Fgshop-dashboard.md",{"name":57,"avatar":1038},{"src":59,"alt":57},{"type":61,"value":1040,"toc":1375},[1041,1043,1046,1069,1071,1076,1078,1084,1086,1090,1095,1109,1119,1184,1186,1204,1206,1210,1223,1232,1237,1284,1286,1288,1372],[64,1042,66],{"id":66},[68,1044,1045],{},"gshop-dashboard 是電商後台管理介面，提供商品上架、訂單管理、會員查詢等功能，使用 Nuxt.js SSR 渲染，確保 SEO 與首屏載入效能。",[72,1047,1048,1054,1059,1063],{},[75,1049,1050,1053],{},[78,1051,1052],{},"Framework","：Nuxt.js（SSR 模式）",[75,1055,1056,1058],{},[78,1057,86],{},"：3002",[75,1060,1061,99],{},[78,1062,98],{},[75,1064,1065,1068],{},[78,1066,1067],{},"網域","：dashboard.garydemo.com",[64,1070,108],{"id":108},[110,1072,1074],{"className":1073,"code":114,"language":115},[113],[117,1075,114],{"__ignoreMap":119},[68,1077,122],{},[110,1079,1082],{"className":1080,"code":1081,"language":115},[113],"Cloudflare → GCP Load Balancer → Ingress (Host: dashboard.garydemo.com) → gshop-dashboard Pod\n",[117,1083,1081],{"__ignoreMap":119},[64,1085,131],{"id":131},[133,1087,1089],{"id":1088},"_1-502-bad-gateway-健康檢查失敗","1. 502 Bad Gateway — 健康檢查失敗",[68,1091,1092,1094],{},[78,1093,141],{},"：部署完成後，dashboard 持續回傳 502。",[68,1096,1097,315,1099,1101,1102,1104,1105,1108],{},[78,1098,151],{},[117,1100,318],{}," 做健康檢查，但 Nuxt SSR 的 ",[117,1103,322],{}," 會 302 redirect 到 ",[117,1106,1107],{},"\u002Flogin","。GCP LB 不把 302 算成健康狀態，因此判定服務異常，停止轉發流量。",[68,1110,1111,328,1113,1115,1116,1118],{},[78,1112,161],{},[117,1114,331],{},"，直接將健康檢查路徑指定為 ",[117,1117,1107],{},"（回傳 200）：",[110,1120,1122],{"className":339,"code":1121,"language":341,"meta":119,"style":119},"apiVersion: cloud.google.com\u002Fv1\nkind: BackendConfig\nmetadata:\n  name: gshop-dashboard-backend-config\nspec:\n  healthCheck:\n    requestPath: \u002Flogin\n    type: HTTP\n",[117,1123,1124,1132,1140,1146,1155,1161,1167,1176],{"__ignoreMap":119},[180,1125,1126,1128,1130],{"class":182,"line":183},[180,1127,349],{"class":348},[180,1129,253],{"class":190},[180,1131,354],{"class":197},[180,1133,1134,1136,1138],{"class":182,"line":204},[180,1135,359],{"class":348},[180,1137,253],{"class":190},[180,1139,364],{"class":197},[180,1141,1142,1144],{"class":182,"line":223},[180,1143,369],{"class":348},[180,1145,372],{"class":190},[180,1147,1148,1150,1152],{"class":182,"line":287},[180,1149,377],{"class":348},[180,1151,253],{"class":190},[180,1153,1154],{"class":197}," gshop-dashboard-backend-config\n",[180,1156,1157,1159],{"class":182,"line":385},[180,1158,388],{"class":348},[180,1160,372],{"class":190},[180,1162,1163,1165],{"class":182,"line":393},[180,1164,396],{"class":348},[180,1166,372],{"class":190},[180,1168,1169,1171,1173],{"class":182,"line":401},[180,1170,404],{"class":348},[180,1172,253],{"class":190},[180,1174,1175],{"class":197}," \u002Flogin\n",[180,1177,1178,1180,1182],{"class":182,"line":412},[180,1179,415],{"class":348},[180,1181,253],{"class":190},[180,1183,420],{"class":197},[68,1185,423],{},[110,1187,1189],{"className":339,"code":1188,"language":341,"meta":119,"style":119},"cloud.google.com\u002Fbackend-config: '{\"default\": \"gshop-dashboard-backend-config\"}'\n",[117,1190,1191],{"__ignoreMap":119},[180,1192,1193,1195,1197,1199,1202],{"class":182,"line":183},[180,1194,433],{"class":348},[180,1196,253],{"class":190},[180,1198,438],{"class":190},[180,1200,1201],{"class":197},"{\"default\": \"gshop-dashboard-backend-config\"}",[180,1203,444],{"class":190},[300,1205],{},[133,1207,1209],{"id":1208},"_2-ingress-長時間無法取得-ipneg-not-ready","2. Ingress 長時間無法取得 IP（NEG not ready）",[68,1211,1212,1214,1215,1218,1219,1222],{},[78,1213,141],{},"：套用 ",[117,1216,1217],{},"ingress.yaml"," 後，",[117,1220,1221],{},"kubectl get ingress"," 顯示 IP 欄位一直是空的。",[68,1224,1225,1227,1228,1231],{},[78,1226,151],{},"：Service 上殘留舊的 ",[117,1229,1230],{},"networking.gke.io\u002Ftarget-pool"," annotation，與 GKE Ingress Controller 的 NEG（Network Endpoint Group）機制衝突，導致 LB backend 無法正常建立。",[68,1233,1234,1236],{},[78,1235,161],{},"：移除舊 annotation，刪除後重建 Ingress：",[110,1238,1240],{"className":174,"code":1239,"language":176,"meta":119,"style":119},"kubectl annotate svc gshop-dashboard networking.gke.io\u002Ftarget-pool-\nkubectl delete ingress gshop-ingress\nkubectl apply -f k8s\u002Fingress.yaml\n",[117,1241,1242,1258,1271],{"__ignoreMap":119},[180,1243,1244,1246,1249,1252,1255],{"class":182,"line":183},[180,1245,208],{"class":207},[180,1247,1248],{"class":197}," annotate",[180,1250,1251],{"class":197}," svc",[180,1253,1254],{"class":197}," gshop-dashboard",[180,1256,1257],{"class":197}," networking.gke.io\u002Ftarget-pool-\n",[180,1259,1260,1262,1265,1268],{"class":182,"line":204},[180,1261,208],{"class":207},[180,1263,1264],{"class":197}," delete",[180,1266,1267],{"class":197}," ingress",[180,1269,1270],{"class":197}," gshop-ingress\n",[180,1272,1273,1275,1278,1281],{"class":182,"line":223},[180,1274,208],{"class":207},[180,1276,1277],{"class":197}," apply",[180,1279,1280],{"class":197}," -f",[180,1282,1283],{"class":197}," k8s\u002Fingress.yaml\n",[64,1285,518],{"id":517},[68,1287,521],{},[110,1289,1291],{"className":339,"code":1290,"language":341,"meta":119,"style":119},"- name: Build and push image\n  run: |\n    docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fdashboard:latest .\n    docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fdashboard:latest\n\n- uses: google-github-actions\u002Fget-gke-credentials@v2\n  with:\n    cluster_name: gshop-cluster\n    location: asia-east1\n\n- run: kubectl rollout restart deployment\u002Fgshop-dashboard\n",[117,1292,1293,1303,1311,1316,1321,1325,1335,1341,1349,1357,1361],{"__ignoreMap":119},[180,1294,1295,1297,1299,1301],{"class":182,"line":183},[180,1296,531],{"class":190},[180,1298,568],{"class":348},[180,1300,253],{"class":190},[180,1302,573],{"class":197},[180,1304,1305,1307,1309],{"class":182,"line":204},[180,1306,578],{"class":348},[180,1308,253],{"class":190},[180,1310,584],{"class":583},[180,1312,1313],{"class":182,"line":223},[180,1314,1315],{"class":197},"    docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fdashboard:latest .\n",[180,1317,1318],{"class":182,"line":287},[180,1319,1320],{"class":197},"    docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fdashboard:latest\n",[180,1322,1323],{"class":182,"line":385},[180,1324,561],{"emptyLinePlaceholder":47},[180,1326,1327,1329,1331,1333],{"class":182,"line":393},[180,1328,531],{"class":190},[180,1330,534],{"class":348},[180,1332,253],{"class":190},[180,1334,611],{"class":197},[180,1336,1337,1339],{"class":182,"line":401},[180,1338,544],{"class":348},[180,1340,372],{"class":190},[180,1342,1343,1345,1347],{"class":182,"line":412},[180,1344,624],{"class":348},[180,1346,253],{"class":190},[180,1348,629],{"class":197},[180,1350,1351,1353,1355],{"class":182,"line":597},[180,1352,635],{"class":348},[180,1354,253],{"class":190},[180,1356,640],{"class":197},[180,1358,1359],{"class":182,"line":602},[180,1360,561],{"emptyLinePlaceholder":47},[180,1362,1363,1365,1367,1369],{"class":182,"line":614},[180,1364,531],{"class":190},[180,1366,653],{"class":348},[180,1368,253],{"class":190},[180,1370,1371],{"class":197}," kubectl rollout restart deployment\u002Fgshop-dashboard\n",[663,1373,1374],{},"html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":119,"searchDepth":204,"depth":204,"links":1376},[1377,1378,1379,1383],{"id":66,"depth":204,"text":66},{"id":108,"depth":204,"text":108},{"id":131,"depth":204,"text":131,"children":1380},[1381,1382],{"id":1088,"depth":223,"text":1089},{"id":1208,"depth":223,"text":1209},{"id":517,"depth":204,"text":518},"訪客唯讀帳號：demouser@gmail.com \u002F demo1234","https:\u002F\u002Fdashboard.garydemo.com\u002F","使用 Nuxt.js SSR 建構電商管理後台，部署於 GKE Autopilot，透過 GitHub Actions 自動化部署。","\u002Fprojects\u002Fdashboard.jpg",{},"\u002Fprojects\u002Fgshop-dashboard",{"title":748,"description":1386},"projects\u002Fgshop-dashboard",[1393,1394,686,687,688],"Nuxt.js","SSR","https:\u002F\u002Fgithub.com\u002Fvery-cool-gshop\u002Fgshop-dashboard","bMpiypcjydvK_1Oqfq0u_yVq9Xqm78E6ysep5TbJv6U",{"id":1398,"title":759,"author":1399,"body":1401,"date":676,"demoNote":37,"demoUrl":1752,"description":1753,"extension":678,"image":1754,"meta":1755,"navigation":47,"path":1756,"seo":1757,"stem":1758,"tags":1759,"url":1760,"__hash__":1761},"projectPages\u002Fprojects\u002Fgshop-web.md",{"name":57,"avatar":1400},{"src":59,"alt":57},{"type":61,"value":1402,"toc":1742},[1403,1405,1408,1434,1436,1442,1444,1450,1452,1456,1465,1473,1481,1483,1487,1492,1497,1501,1510,1538,1550,1596,1598,1602,1609,1618,1623,1651,1653,1655,1739],[64,1404,66],{"id":66},[68,1406,1407],{},"gshop-web 是面向消費者的電商購物前台，提供商品瀏覽、加入購物車、結帳等功能，使用 Nuxt.js SSR 確保 SEO 效果與首屏載入速度。",[72,1409,1410,1414,1419,1423,1428],{},[75,1411,1412,1053],{},[78,1413,1052],{},[75,1415,1416,1418],{},[78,1417,86],{},"：3003",[75,1420,1421,99],{},[78,1422,98],{},[75,1424,1425,1427],{},[78,1426,1067],{},"：web.garydemo.com",[75,1429,1430,1433],{},[78,1431,1432],{},"CDN \u002F Proxy","：Cloudflare（Full Strict TLS）",[64,1435,108],{"id":108},[110,1437,1440],{"className":1438,"code":1439,"language":115},[113],"GitHub (main) → GitHub Actions → docker build (amd64) → Artifact Registry\n                                                               ↓\n                                                       GKE pull image\n                                                               ↓\n                                                  kubectl rollout restart\n",[117,1441,1439],{"__ignoreMap":119},[68,1443,122],{},[110,1445,1448],{"className":1446,"code":1447,"language":115},[113],"使用者瀏覽器\n    ↓ HTTPS\nCloudflare Proxy（DNS + TLS 終止）\n    ↓ HTTPS + Origin Certificate\nGCP Load Balancer（34.160.168.110）\n    ↓\nIngress（Host: web.garydemo.com）\n    ↓\ngshop-web Pod（:3003）\n",[117,1449,1447],{"__ignoreMap":119},[64,1451,131],{"id":131},[133,1453,1455],{"id":1454},"_1-本地-build-的-image-在-gke-無法執行","1. 本地 Build 的 Image 在 GKE 無法執行",[68,1457,1458,1460,1461,1464],{},[78,1459,141],{},"：本地 ",[117,1462,1463],{},"docker build"," 後推上去，GKE Pod 啟動失敗。",[68,1466,1467,1469,1470,1472],{},[78,1468,151],{},"：開發機是 Apple Silicon（arm64），",[117,1471,1463],{}," 預設產出 arm64 image，但 GKE 節點是 amd64 架構，無法執行。",[68,1474,1475,1477,1478,1480],{},[78,1476,161],{},"：CI\u002FCD 改用 GitHub Actions ubuntu runner 執行 ",[117,1479,1463],{},"，runner 本身是 amd64，build 出的 image 天生就是 amd64，不需要額外設定或使用 Cloud Build。",[300,1482],{},[133,1484,1486],{"id":1485},"_2-cloudflare-full-strict-tls-設定","2. Cloudflare Full (Strict) TLS 設定",[68,1488,1489,1491],{},[78,1490,141],{},"：設定 Cloudflare SSL\u002FTLS 為 Full (Strict) 後，網站無法正常載入。",[68,1493,1494,1496],{},[78,1495,151],{},"：Full (Strict) 要求 Cloudflare 到 GCP 這段也必須使用合法憑證，自簽憑證不被接受。",[68,1498,1499,336],{},[78,1500,161],{},[1502,1503,1504,1507],"ol",{},[75,1505,1506],{},"在 Cloudflare Dashboard → SSL\u002FTLS → Origin Server 建立 Origin Certificate 並下載",[75,1508,1509],{},"存為 Kubernetes TLS Secret：",[110,1511,1512],{"className":174,"code":873,"language":176,"meta":119,"style":119},[117,1513,1514,1528,1534],{"__ignoreMap":119},[180,1515,1516,1518,1520,1522,1524,1526],{"class":182,"line":183},[180,1517,208],{"class":207},[180,1519,882],{"class":197},[180,1521,214],{"class":197},[180,1523,887],{"class":197},[180,1525,890],{"class":197},[180,1527,220],{"class":186},[180,1529,1530,1532],{"class":182,"line":204},[180,1531,897],{"class":197},[180,1533,220],{"class":186},[180,1535,1536],{"class":182,"line":223},[180,1537,904],{"class":197},[1502,1539,1540],{"start":223},[75,1541,1542,1543,1545,1546,1549],{},"在 ",[117,1544,1217],{}," 的 ",[117,1547,1548],{},"tls"," 區塊掛載：",[110,1551,1553],{"className":339,"code":1552,"language":341,"meta":119,"style":119},"spec:\n  tls:\n    - secretName: cloudflare-origin-cert\n      hosts:\n        - web.garydemo.com\n",[117,1554,1555,1561,1568,1581,1588],{"__ignoreMap":119},[180,1556,1557,1559],{"class":182,"line":183},[180,1558,388],{"class":348},[180,1560,372],{"class":190},[180,1562,1563,1566],{"class":182,"line":204},[180,1564,1565],{"class":348},"  tls",[180,1567,372],{"class":190},[180,1569,1570,1573,1576,1578],{"class":182,"line":223},[180,1571,1572],{"class":190},"    -",[180,1574,1575],{"class":348}," secretName",[180,1577,253],{"class":190},[180,1579,1580],{"class":197}," cloudflare-origin-cert\n",[180,1582,1583,1586],{"class":182,"line":287},[180,1584,1585],{"class":348},"      hosts",[180,1587,372],{"class":190},[180,1589,1590,1593],{"class":182,"line":385},[180,1591,1592],{"class":190},"        -",[180,1594,1595],{"class":197}," web.garydemo.com\n",[300,1597],{},[133,1599,1601],{"id":1600},"_3-ingress-class-設定錯誤","3. Ingress Class 設定錯誤",[68,1603,1604,1214,1606,1608],{},[78,1605,141],{},[117,1607,1217],{}," 後，GKE 沒有建立對應的 Cloud Load Balancer。",[68,1610,1611,1613,1614,1617],{},[78,1612,151],{},"：使用了 ",[117,1615,1616],{},"spec.ingressClassName: gce","，GKE Ingress Controller 不認識這個欄位。",[68,1619,1620,1622],{},[78,1621,161],{},"：改用 annotation 方式指定：",[110,1624,1626],{"className":339,"code":1625,"language":341,"meta":119,"style":119},"metadata:\n  annotations:\n    kubernetes.io\u002Fingress.class: gce\n",[117,1627,1628,1634,1641],{"__ignoreMap":119},[180,1629,1630,1632],{"class":182,"line":183},[180,1631,369],{"class":348},[180,1633,372],{"class":190},[180,1635,1636,1639],{"class":182,"line":204},[180,1637,1638],{"class":348},"  annotations",[180,1640,372],{"class":190},[180,1642,1643,1646,1648],{"class":182,"line":223},[180,1644,1645],{"class":348},"    kubernetes.io\u002Fingress.class",[180,1647,253],{"class":190},[180,1649,1650],{"class":197}," gce\n",[64,1652,518],{"id":517},[68,1654,521],{},[110,1656,1658],{"className":339,"code":1657,"language":341,"meta":119,"style":119},"- name: Build and push image\n  run: |\n    docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fweb:latest .\n    docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fweb:latest\n\n- uses: google-github-actions\u002Fget-gke-credentials@v2\n  with:\n    cluster_name: gshop-cluster\n    location: asia-east1\n\n- run: kubectl rollout restart deployment\u002Fgshop-web\n",[117,1659,1660,1670,1678,1683,1688,1692,1702,1708,1716,1724,1728],{"__ignoreMap":119},[180,1661,1662,1664,1666,1668],{"class":182,"line":183},[180,1663,531],{"class":190},[180,1665,568],{"class":348},[180,1667,253],{"class":190},[180,1669,573],{"class":197},[180,1671,1672,1674,1676],{"class":182,"line":204},[180,1673,578],{"class":348},[180,1675,253],{"class":190},[180,1677,584],{"class":583},[180,1679,1680],{"class":182,"line":223},[180,1681,1682],{"class":197},"    docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fweb:latest .\n",[180,1684,1685],{"class":182,"line":287},[180,1686,1687],{"class":197},"    docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fweb:latest\n",[180,1689,1690],{"class":182,"line":385},[180,1691,561],{"emptyLinePlaceholder":47},[180,1693,1694,1696,1698,1700],{"class":182,"line":393},[180,1695,531],{"class":190},[180,1697,534],{"class":348},[180,1699,253],{"class":190},[180,1701,611],{"class":197},[180,1703,1704,1706],{"class":182,"line":401},[180,1705,544],{"class":348},[180,1707,372],{"class":190},[180,1709,1710,1712,1714],{"class":182,"line":412},[180,1711,624],{"class":348},[180,1713,253],{"class":190},[180,1715,629],{"class":197},[180,1717,1718,1720,1722],{"class":182,"line":597},[180,1719,635],{"class":348},[180,1721,253],{"class":190},[180,1723,640],{"class":197},[180,1725,1726],{"class":182,"line":602},[180,1727,561],{"emptyLinePlaceholder":47},[180,1729,1730,1732,1734,1736],{"class":182,"line":614},[180,1731,531],{"class":190},[180,1733,653],{"class":348},[180,1735,253],{"class":190},[180,1737,1738],{"class":197}," kubectl rollout restart deployment\u002Fgshop-web\n",[663,1740,1741],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":119,"searchDepth":204,"depth":204,"links":1743},[1744,1745,1746,1751],{"id":66,"depth":204,"text":66},{"id":108,"depth":204,"text":108},{"id":131,"depth":204,"text":131,"children":1747},[1748,1749,1750],{"id":1454,"depth":223,"text":1455},{"id":1485,"depth":223,"text":1486},{"id":1600,"depth":223,"text":1601},{"id":517,"depth":204,"text":518},"https:\u002F\u002Fweb.garydemo.com\u002F","使用 Nuxt.js SSR 建構電商購物前台，部署於 GKE Autopilot，透過 Cloudflare Proxy 以 Full (Strict) TLS 對外提供服務。","\u002Fprojects\u002Fweb.jpg",{},"\u002Fprojects\u002Fgshop-web",{"title":759,"description":1753},"projects\u002Fgshop-web",[1393,1394,686,1031,688],"https:\u002F\u002Fgithub.com\u002Fvery-cool-gshop\u002Fgshop-web","Y2r55f-GrNR-VQIkOcmrEz1gTxsgdUtUKIlgVOOf-l8",1782321732707]