[{"data":1,"prerenderedAt":441},["ShallowReactive",2],{"navigation":3,"\u002Fprojects\u002Fgshop-architecture":54},[4],{"title":5,"path":6,"stem":7,"children":8,"page":53},"Articles","\u002Farticles","articles",[9,13,17,21,25,29,33,37,41,45,49],{"title":10,"path":11,"stem":12},"Async 到底在解決什麼問題？","\u002Farticles\u002Fasync-waiting","articles\u002Fasync-waiting",{"title":14,"path":15,"stem":16},"Callback 是什麼？","\u002Farticles\u002Fcallback","articles\u002Fcallback",{"title":18,"path":19,"stem":20},"Claude Code 的 Skill、MCP、Hook、Plugin 分別是什麼？","\u002Farticles\u002Fclaude-code-skill-mcp-hook-plugin","articles\u002Fclaude-code-skill-mcp-hook-plugin",{"title":22,"path":23,"stem":24},"用 Daily Snapshot 提升統計查詢速度","\u002Farticles\u002Fdaily-snapshot","articles\u002Fdaily-snapshot",{"title":26,"path":27,"stem":28},"GKE 部署","\u002Farticles\u002Fgke-deployment","articles\u002Fgke-deployment",{"title":30,"path":31,"stem":32},"I\u002FO 密集 vs CPU 密集：執行緒數該開多少？","\u002Farticles\u002Fio-bound-vs-cpu-bound","articles\u002Fio-bound-vs-cpu-bound",{"title":34,"path":35,"stem":36},"從 LLM 到 Agent：打通底層邏輯","\u002Farticles\u002Fllm-to-agent","articles\u002Fllm-to-agent",{"title":38,"path":39,"stem":40},"資訊安全實踐","\u002Farticles\u002Fsecurity-best-practices","articles\u002Fsecurity-best-practices",{"title":42,"path":43,"stem":44},"單機架構的性能優化","\u002Farticles\u002Fsingle-machine-performance","articles\u002Fsingle-machine-performance",{"title":46,"path":47,"stem":48},"伺服器渲染 SSR","\u002Farticles\u002Fssr","articles\u002Fssr",{"title":50,"path":51,"stem":52},"同步 vs 非同步","\u002Farticles\u002Fsync-vs-async","articles\u002Fsync-vs-async",false,{"id":55,"title":56,"author":57,"body":61,"date":422,"demoNote":423,"demoUrl":423,"description":424,"extension":425,"image":426,"meta":427,"navigation":428,"path":429,"seo":430,"stem":431,"tags":432,"url":439,"__hash__":440},"projectPages\u002Fprojects\u002Fgshop-architecture.md","GShop 系統架構",{"name":58,"avatar":59},"Gary",{"src":60,"alt":58},"\u002Fimages\u002Fselfie.webp",{"type":62,"value":63,"toc":407},"minimark",[64,72,76,79,135,138,141,145,153,163,170,172,176,179,185,191,204,213,215,219,230,235,241,244,248,251,298,301,305,308,344,347,349,352,356,363,369,379,383,389,391,394,397,403],[65,66,67],"p",{},[68,69],"img",{"alt":70,"src":71},"GShop 系統架構圖","\u002Fimages\u002Fstructure.png",[73,74,75],"h2",{"id":75},"系統概覽",[65,77,78],{},"GShop 是一套電商系統，由三個獨立服務組成，統一部署在 GKE Autopilot 上：",[80,81,82,98],"table",{},[83,84,85],"thead",{},[86,87,88,92,95],"tr",{},[89,90,91],"th",{},"服務",[89,93,94],{},"角色",[89,96,97],{},"Port",[99,100,101,113,124],"tbody",{},[86,102,103,107,110],{},[104,105,106],"td",{},"gshop-api",[104,108,109],{},"Node.js REST API",[104,111,112],{},"3001",[86,114,115,118,121],{},[104,116,117],{},"gshop-dashboard",[104,119,120],{},"Nuxt.js 管理後台",[104,122,123],{},"3002",[86,125,126,129,132],{},[104,127,128],{},"gshop-web",[104,130,131],{},"Nuxt.js 前台購物網站",[104,133,134],{},"3003",[65,136,137],{},"三個服務共用同一個 GKE Cluster、同一個 Cloud Load Balancer IP，由 Ingress 依 Host header 分流。",[139,140],"hr",{},[73,142,144],{"id":143},"cicd-pipeline","CI\u002FCD Pipeline",[65,146,147,148,152],{},"三個 repo 分別位於 GitHub Org ",[149,150,151],"code",{},"very-cool-gshop","，各自有獨立的 GitHub Actions workflow。",[154,155,160],"pre",{"className":156,"code":158,"language":159},[157],"language-text","push main → GitHub Actions\n              ↓\n         docker build (ubuntu-latest \u002F amd64)\n              ↓\n         push → Artifact Registry\n              ↓\n         kubectl rollout restart\n","text",[149,161,158],{"__ignoreMap":162},"",[65,164,165,166,169],{},"GitHub Actions runner 是 ",[149,167,168],{},"ubuntu-latest","，天生就是 amd64，build 出的 image 直接相容 GKE，不需要 Cloud Build。",[139,171],{},[73,173,175],{"id":174},"user-edge-層","User & Edge 層",[65,177,178],{},"使用者請求先經過 Cloudflare，再進入 GCP：",[154,180,183],{"className":181,"code":182,"language":159},[157],"User Browser\n     ↓ HTTPS\n  Cloudflare\n  DNS + Proxy（Full Strict）\n     ↓ HTTPS\nGCP Cloud Load Balancer\n",[149,184,182],{"__ignoreMap":162},[65,186,187],{},[188,189,190],"strong",{},"Cloudflare 的作用：",[192,193,194,198,201],"ul",{},[195,196,197],"li",{},"DNS 解析 — 對外暴露 Cloudflare IP，不直接暴露 GCP 的真實 IP",[195,199,200],{},"Proxy — 所有流量過 Cloudflare 節點，提供 DDoS 防護與快取",[195,202,203],{},"TLS 終止 — 瀏覽器到 Cloudflare 這段的 HTTPS 由 Cloudflare 處理",[65,205,206,209,212],{},[188,207,208],{},"Full (Strict) 模式：",[210,211],"br",{},"\nCloudflare 到 GCP 這段也必須有合法憑證。使用 Cloudflare Origin Certificate，存成 K8s TLS Secret，由 Ingress 掛載使用。",[139,214],{},[73,216,218],{"id":217},"gke-autopilot-叢集","GKE Autopilot 叢集",[65,220,221,222,225,226,229],{},"叢集名稱 ",[149,223,224],{},"gshop-cluster","，部署在 ",[149,227,228],{},"asia-east1","。",[231,232,234],"h3",{"id":233},"ingress-host-based-路由","Ingress — host-based 路由",[154,236,239],{"className":237,"code":238,"language":159},[157],"api.garydemo.com      →  gshop-api:3001\ndashboard.garydemo.com →  gshop-dashboard:3002\nweb.garydemo.com      →  gshop-web:3003\n",[149,240,238],{"__ignoreMap":162},[65,242,243],{},"Cloud Load Balancer（IP：34.160.168.110）接收所有流量，Ingress Controller 根據 Host header 決定轉發目標。",[231,245,247],{"id":246},"tls-secret","TLS Secret",[65,249,250],{},"Cloudflare Origin Certificate 存成 K8s TLS Secret：",[154,252,256],{"className":253,"code":254,"language":255,"meta":162,"style":162},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","kubectl create secret tls cloudflare-origin-cert \\\n  --cert=certificate.pem \\\n  --key=private.key\n","bash",[149,257,258,284,292],{"__ignoreMap":162},[259,260,263,267,271,274,277,280],"span",{"class":261,"line":262},"line",1,[259,264,266],{"class":265},"sBMFI","kubectl",[259,268,270],{"class":269},"sfazB"," create",[259,272,273],{"class":269}," secret",[259,275,276],{"class":269}," tls",[259,278,279],{"class":269}," cloudflare-origin-cert",[259,281,283],{"class":282},"sTEyZ"," \\\n",[259,285,287,290],{"class":261,"line":286},2,[259,288,289],{"class":269},"  --cert=certificate.pem",[259,291,283],{"class":282},[259,293,295],{"class":261,"line":294},3,[259,296,297],{"class":269},"  --key=private.key\n",[65,299,300],{},"Ingress 掛載這個 Secret，讓 Cloudflare → GCP 這段走 HTTPS。",[231,302,304],{"id":303},"db-secret","DB Secret",[65,306,307],{},"資料庫連線字串存成 K8s Generic Secret：",[154,309,311],{"className":253,"code":310,"language":255,"meta":162,"style":162},"kubectl create secret generic gshop-api-secret \\\n  --from-literal=DATABASE_URL=\"postgresql:\u002F\u002F...\"\n",[149,312,313,329],{"__ignoreMap":162},[259,314,315,317,319,321,324,327],{"class":261,"line":262},[259,316,266],{"class":265},[259,318,270],{"class":269},[259,320,273],{"class":269},[259,322,323],{"class":269}," generic",[259,325,326],{"class":269}," gshop-api-secret",[259,328,283],{"class":282},[259,330,331,334,338,341],{"class":261,"line":286},[259,332,333],{"class":269},"  --from-literal=DATABASE_URL=",[259,335,337],{"class":336},"sMK4o","\"",[259,339,340],{"class":269},"postgresql:\u002F\u002F...",[259,342,343],{"class":336},"\"\n",[65,345,346],{},"gshop-api Pod 透過環境變數讀取，不會出現在 image 或 code 裡。",[139,348],{},[73,350,351],{"id":351},"外部服務",[231,353,355],{"id":354},"supabase-postgresql","Supabase PostgreSQL",[65,357,358,359,362],{},"gshop-api 透過 ",[188,360,361],{},"Session Pooler"," 連線，走 IPv4：",[154,364,367],{"className":365,"code":366,"language":159},[157],"aws-1-ap-southeast-1.pooler.supabase.com:5432\n",[149,368,366],{"__ignoreMap":162},[370,371,372],"blockquote",{},[65,373,374,375,378],{},"GKE Autopilot 節點只有 IPv4，Supabase 直連（",[149,376,377],{},"db.xxx.supabase.co","）是 IPv6 only，直接連會 ENOTFOUND。",[231,380,382],{"id":381},"google-cloud-storage","Google Cloud Storage",[65,384,385,386,229],{},"gshop-api 負責商品圖片的上傳與讀取，存放在 GCS Bucket ",[149,387,388],{},"gshop-files",[139,390],{},[73,392,393],{"id":393},"部署架構圖",[65,395,396],{},"整體流量與部署流向：",[154,398,401],{"className":399,"code":400,"language":159},[157],"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",[149,402,400],{"__ignoreMap":162},[404,405,406],"style",{},"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":162,"searchDepth":286,"depth":286,"links":408},[409,410,411,412,417,421],{"id":75,"depth":286,"text":75},{"id":143,"depth":286,"text":144},{"id":174,"depth":286,"text":175},{"id":217,"depth":286,"text":218,"children":413},[414,415,416],{"id":233,"depth":294,"text":234},{"id":246,"depth":294,"text":247},{"id":303,"depth":294,"text":304},{"id":351,"depth":286,"text":351,"children":418},[419,420],{"id":354,"depth":294,"text":355},{"id":381,"depth":294,"text":382},{"id":393,"depth":286,"text":393},"2026-06-20",null,"三服務電商系統的完整部署藍圖，從 CI\u002FCD 到 GKE 叢集、Cloudflare Proxy 到 Supabase 的全端架構設計。","md","\u002Fimages\u002Farchitecture.jpg",{},true,"\u002Fprojects\u002Fgshop-architecture",{"title":56,"description":424},"projects\u002Fgshop-architecture",[433,434,435,436,437,438],"GKE","Kubernetes","Cloudflare","CI\u002FCD","Supabase","GCS","https:\u002F\u002Fgithub.com\u002Forgs\u002Fvery-cool-gshop\u002Frepositories","y4KXw9ivzhVpoxYNezynOrpqVnB3byV5xZlc2eruhsQ",1784010809694]