[{"data":1,"prerenderedAt":491},["ShallowReactive",2],{"navigation":3,"\u002Fprojects\u002Fgshop-dashboard":34},[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,"author":37,"body":41,"date":474,"demoUrl":475,"description":476,"extension":477,"image":478,"meta":479,"navigation":401,"path":480,"seo":481,"stem":482,"tags":483,"url":489,"__hash__":490},"projectPages\u002Fprojects\u002Fgshop-dashboard.md","gshop-dashboard",{"name":38,"avatar":39},"Gary",{"src":40,"alt":38},"\u002Fimages\u002Fselfie.webp",{"type":42,"value":43,"toc":465},"minimark",[44,48,52,81,84,95,98,104,107,112,118,136,149,242,245,266,269,273,286,295,300,351,355,358,461],[45,46,47],"h2",{"id":47},"架構概覽",[49,50,51],"p",{},"gshop-dashboard 是電商後台管理介面，提供商品上架、訂單管理、會員查詢等功能，使用 Nuxt.js SSR 渲染，確保 SEO 與首屏載入效能。",[53,54,55,63,69,75],"ul",{},[56,57,58,62],"li",{},[59,60,61],"strong",{},"Framework","：Nuxt.js（SSR 模式）",[56,64,65,68],{},[59,66,67],{},"Port","：3002",[56,70,71,74],{},[59,72,73],{},"部署環境","：GKE Autopilot，asia-east1",[56,76,77,80],{},[59,78,79],{},"網域","：dashboard.garydemo.com",[45,82,83],{"id":83},"部署架構",[85,86,91],"pre",{"className":87,"code":89,"language":90},[88],"language-text","GitHub (main) → GitHub Actions → docker build → Artifact Registry\n                                                      ↓\n                                              GKE pull image\n                                                      ↓\n                                         kubectl rollout restart\n","text",[92,93,89],"code",{"__ignoreMap":94},"",[49,96,97],{},"流量路徑：",[85,99,102],{"className":100,"code":101,"language":90},[88],"Cloudflare → GCP Load Balancer → Ingress (Host: dashboard.garydemo.com) → gshop-dashboard Pod\n",[92,103,101],{"__ignoreMap":94},[45,105,106],{"id":106},"遇到的問題與解法",[108,109,111],"h3",{"id":110},"_1-502-bad-gateway-健康檢查失敗","1. 502 Bad Gateway — 健康檢查失敗",[49,113,114,117],{},[59,115,116],{},"問題","：部署完成後，dashboard 持續回傳 502。",[49,119,120,123,124,127,128,131,132,135],{},[59,121,122],{},"原因","：GCP Load Balancer 預設對 ",[92,125,126],{},"GET \u002F"," 做健康檢查，但 Nuxt SSR 的 ",[92,129,130],{},"\u002F"," 會 302 redirect 到 ",[92,133,134],{},"\u002Flogin","。GCP LB 不把 302 算成健康狀態，因此判定服務異常，停止轉發流量。",[49,137,138,141,142,145,146,148],{},[59,139,140],{},"解法","：建立 ",[92,143,144],{},"BackendConfig","，直接將健康檢查路徑指定為 ",[92,147,134],{},"（回傳 200）：",[85,150,154],{"className":151,"code":152,"language":153,"meta":94,"style":94},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","apiVersion: cloud.google.com\u002Fv1\nkind: BackendConfig\nmetadata:\n  name: gshop-dashboard-backend-config\nspec:\n  healthCheck:\n    requestPath: \u002Flogin\n    type: HTTP\n","yaml",[92,155,156,173,184,193,204,212,220,231],{"__ignoreMap":94},[157,158,161,165,169],"span",{"class":159,"line":160},"line",1,[157,162,164],{"class":163},"swJcz","apiVersion",[157,166,168],{"class":167},"sMK4o",":",[157,170,172],{"class":171},"sfazB"," cloud.google.com\u002Fv1\n",[157,174,176,179,181],{"class":159,"line":175},2,[157,177,178],{"class":163},"kind",[157,180,168],{"class":167},[157,182,183],{"class":171}," BackendConfig\n",[157,185,187,190],{"class":159,"line":186},3,[157,188,189],{"class":163},"metadata",[157,191,192],{"class":167},":\n",[157,194,196,199,201],{"class":159,"line":195},4,[157,197,198],{"class":163},"  name",[157,200,168],{"class":167},[157,202,203],{"class":171}," gshop-dashboard-backend-config\n",[157,205,207,210],{"class":159,"line":206},5,[157,208,209],{"class":163},"spec",[157,211,192],{"class":167},[157,213,215,218],{"class":159,"line":214},6,[157,216,217],{"class":163},"  healthCheck",[157,219,192],{"class":167},[157,221,223,226,228],{"class":159,"line":222},7,[157,224,225],{"class":163},"    requestPath",[157,227,168],{"class":167},[157,229,230],{"class":171}," \u002Flogin\n",[157,232,234,237,239],{"class":159,"line":233},8,[157,235,236],{"class":163},"    type",[157,238,168],{"class":167},[157,240,241],{"class":171}," HTTP\n",[49,243,244],{},"並在 Service 加上 annotation：",[85,246,248],{"className":151,"code":247,"language":153,"meta":94,"style":94},"cloud.google.com\u002Fbackend-config: '{\"default\": \"gshop-dashboard-backend-config\"}'\n",[92,249,250],{"__ignoreMap":94},[157,251,252,255,257,260,263],{"class":159,"line":160},[157,253,254],{"class":163},"cloud.google.com\u002Fbackend-config",[157,256,168],{"class":167},[157,258,259],{"class":167}," '",[157,261,262],{"class":171},"{\"default\": \"gshop-dashboard-backend-config\"}",[157,264,265],{"class":167},"'\n",[267,268],"hr",{},[108,270,272],{"id":271},"_2-ingress-長時間無法取得-ipneg-not-ready","2. Ingress 長時間無法取得 IP（NEG not ready）",[49,274,275,277,278,281,282,285],{},[59,276,116],{},"：套用 ",[92,279,280],{},"ingress.yaml"," 後，",[92,283,284],{},"kubectl get ingress"," 顯示 IP 欄位一直是空的。",[49,287,288,290,291,294],{},[59,289,122],{},"：Service 上殘留舊的 ",[92,292,293],{},"networking.gke.io\u002Ftarget-pool"," annotation，與 GKE Ingress Controller 的 NEG（Network Endpoint Group）機制衝突，導致 LB backend 無法正常建立。",[49,296,297,299],{},[59,298,140],{},"：移除舊 annotation，刪除後重建 Ingress：",[85,301,305],{"className":302,"code":303,"language":304,"meta":94,"style":94},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","kubectl annotate svc gshop-dashboard networking.gke.io\u002Ftarget-pool-\nkubectl delete ingress gshop-ingress\nkubectl apply -f k8s\u002Fingress.yaml\n","bash",[92,306,307,325,338],{"__ignoreMap":94},[157,308,309,313,316,319,322],{"class":159,"line":160},[157,310,312],{"class":311},"sBMFI","kubectl",[157,314,315],{"class":171}," annotate",[157,317,318],{"class":171}," svc",[157,320,321],{"class":171}," gshop-dashboard",[157,323,324],{"class":171}," networking.gke.io\u002Ftarget-pool-\n",[157,326,327,329,332,335],{"class":159,"line":175},[157,328,312],{"class":311},[157,330,331],{"class":171}," delete",[157,333,334],{"class":171}," ingress",[157,336,337],{"class":171}," gshop-ingress\n",[157,339,340,342,345,348],{"class":159,"line":186},[157,341,312],{"class":311},[157,343,344],{"class":171}," apply",[157,346,347],{"class":171}," -f",[157,349,350],{"class":171}," k8s\u002Fingress.yaml\n",[45,352,354],{"id":353},"cicd-設定","CI\u002FCD 設定",[49,356,357],{},"每次 push 到 main，GitHub Actions 自動執行：",[85,359,361],{"className":151,"code":360,"language":153,"meta":94,"style":94},"- 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",[92,362,363,376,387,392,397,403,415,422,432,443,448],{"__ignoreMap":94},[157,364,365,368,371,373],{"class":159,"line":160},[157,366,367],{"class":167},"-",[157,369,370],{"class":163}," name",[157,372,168],{"class":167},[157,374,375],{"class":171}," Build and push image\n",[157,377,378,381,383],{"class":159,"line":175},[157,379,380],{"class":163},"  run",[157,382,168],{"class":167},[157,384,386],{"class":385},"s7zQu"," |\n",[157,388,389],{"class":159,"line":186},[157,390,391],{"class":171},"    docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fdashboard:latest .\n",[157,393,394],{"class":159,"line":195},[157,395,396],{"class":171},"    docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fdashboard:latest\n",[157,398,399],{"class":159,"line":206},[157,400,402],{"emptyLinePlaceholder":401},true,"\n",[157,404,405,407,410,412],{"class":159,"line":214},[157,406,367],{"class":167},[157,408,409],{"class":163}," uses",[157,411,168],{"class":167},[157,413,414],{"class":171}," google-github-actions\u002Fget-gke-credentials@v2\n",[157,416,417,420],{"class":159,"line":222},[157,418,419],{"class":163},"  with",[157,421,192],{"class":167},[157,423,424,427,429],{"class":159,"line":233},[157,425,426],{"class":163},"    cluster_name",[157,428,168],{"class":167},[157,430,431],{"class":171}," gshop-cluster\n",[157,433,435,438,440],{"class":159,"line":434},9,[157,436,437],{"class":163},"    location",[157,439,168],{"class":167},[157,441,442],{"class":171}," asia-east1\n",[157,444,446],{"class":159,"line":445},10,[157,447,402],{"emptyLinePlaceholder":401},[157,449,451,453,456,458],{"class":159,"line":450},11,[157,452,367],{"class":167},[157,454,455],{"class":163}," run",[157,457,168],{"class":167},[157,459,460],{"class":171}," kubectl rollout restart deployment\u002Fgshop-dashboard\n",[462,463,464],"style",{},"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":94,"searchDepth":175,"depth":175,"links":466},[467,468,469,473],{"id":47,"depth":175,"text":47},{"id":83,"depth":175,"text":83},{"id":106,"depth":175,"text":106,"children":470},[471,472],{"id":110,"depth":186,"text":111},{"id":271,"depth":186,"text":272},{"id":353,"depth":175,"text":354},"2026-06-15","https:\u002F\u002Fdashboard.garydemo.com\u002F","使用 Nuxt.js SSR 建構電商管理後台，部署於 GKE Autopilot，透過 GitHub Actions 自動化部署。","md","\u002Fprojects\u002Fdashboard.jpg",{},"\u002Fprojects\u002Fgshop-dashboard",{"title":36,"description":476},"projects\u002Fgshop-dashboard",[484,485,486,487,488],"Nuxt.js","SSR","GKE","Kubernetes","CI\u002FCD","https:\u002F\u002Fgithub.com\u002Fvery-cool-gshop\u002Fgshop-dashboard","y-HIaZTm5o2IN-ZQ_G9da3CjcV0whonX8qxHZiExKCU",1781661891539]