[{"data":1,"prerenderedAt":1463},["ShallowReactive",2],{"navigation":3,"\u002Farticles\u002Fgke-deployment":34,"\u002Farticles\u002Fgke-deployment-surround":1458},[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":14,"author":36,"body":40,"date":1450,"description":1451,"extension":1452,"externalUrl":1453,"image":1454,"meta":1455,"minRead":193,"navigation":898,"path":15,"seo":1456,"stem":16,"__hash__":1457},"blog\u002Farticles\u002Fgke-deployment.md",{"name":37,"avatar":38},"Gary",{"src":39,"alt":37},"\u002Fimages\u002Fselfie.webp",{"type":41,"value":42,"toc":1426},"minimark",[43,47,94,97,100,105,110,204,208,241,245,307,311,315,323,326,328,331,335,353,357,416,420,533,537,619,623,760,762,766,770,812,817,821,832,835,841,843,847,850,856,860,1010,1021,1025,1266,1271,1273,1276,1422],[44,45,46],"h2",{"id":46},"架構",[48,49,50,58,64,70,76,82,88],"ul",{},[51,52,53,57],"li",{},[54,55,56],"strong",{},"gshop-api"," — Node.js\u002FExpress，Port 3001",[51,59,60,63],{},[54,61,62],{},"gshop-dashboard"," — Nuxt.js SSR，Port 3002",[51,65,66,69],{},[54,67,68],{},"gshop-web"," — Nuxt.js SSR，Port 3003",[51,71,72,75],{},[54,73,74],{},"GKE Autopilot Cluster"," — gshop-cluster，asia-east1",[51,77,78,81],{},[54,79,80],{},"Artifact Registry"," — asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop",[51,83,84,87],{},[54,85,86],{},"Database"," — Supabase（Session Pooler）",[51,89,90,93],{},[54,91,92],{},"Domain"," — garydemo.com（Cloudflare）",[95,96],"hr",{},[44,98,99],{"id":99},"部署流程",[101,102,104],"h3",{"id":103},"首次部署一次性","首次部署（一次性）",[106,107,109],"h4",{"id":108},"_1-建立-kubernetes-secret","1. 建立 Kubernetes Secret",[111,112,117],"pre",{"className":113,"code":114,"language":115,"meta":116,"style":116},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","kubectl create secret generic gshop-api-secret \\\n  --from-literal=DATABASE_URL=\"...\" \\\n  --from-literal=JWT_SECRET=\"...\" \\\n  --from-literal=GCS_BUCKET_NAME=\"...\" \\\n  --from-literal=ANTHROPIC_API_KEY=\"...\"\n","bash","",[118,119,120,146,163,177,191],"code",{"__ignoreMap":116},[121,122,125,129,133,136,139,142],"span",{"class":123,"line":124},"line",1,[121,126,128],{"class":127},"sBMFI","kubectl",[121,130,132],{"class":131},"sfazB"," create",[121,134,135],{"class":131}," secret",[121,137,138],{"class":131}," generic",[121,140,141],{"class":131}," gshop-api-secret",[121,143,145],{"class":144},"sTEyZ"," \\\n",[121,147,149,152,156,159,161],{"class":123,"line":148},2,[121,150,151],{"class":131},"  --from-literal=DATABASE_URL=",[121,153,155],{"class":154},"sMK4o","\"",[121,157,158],{"class":131},"...",[121,160,155],{"class":154},[121,162,145],{"class":144},[121,164,166,169,171,173,175],{"class":123,"line":165},3,[121,167,168],{"class":131},"  --from-literal=JWT_SECRET=",[121,170,155],{"class":154},[121,172,158],{"class":131},[121,174,155],{"class":154},[121,176,145],{"class":144},[121,178,180,183,185,187,189],{"class":123,"line":179},4,[121,181,182],{"class":131},"  --from-literal=GCS_BUCKET_NAME=",[121,184,155],{"class":154},[121,186,158],{"class":131},[121,188,155],{"class":154},[121,190,145],{"class":144},[121,192,194,197,199,201],{"class":123,"line":193},5,[121,195,196],{"class":131},"  --from-literal=ANTHROPIC_API_KEY=",[121,198,155],{"class":154},[121,200,158],{"class":131},[121,202,203],{"class":154},"\"\n",[106,205,207],{"id":206},"_2-建立-cloudflare-origin-certificate-tls-secret","2. 建立 Cloudflare Origin Certificate TLS Secret",[111,209,211],{"className":113,"code":210,"language":115,"meta":116,"style":116},"kubectl create secret tls cloudflare-origin-cert \\\n  --cert=certificate.pem \\\n  --key=private.key\n",[118,212,213,229,236],{"__ignoreMap":116},[121,214,215,217,219,221,224,227],{"class":123,"line":124},[121,216,128],{"class":127},[121,218,132],{"class":131},[121,220,135],{"class":131},[121,222,223],{"class":131}," tls",[121,225,226],{"class":131}," cloudflare-origin-cert",[121,228,145],{"class":144},[121,230,231,234],{"class":123,"line":148},[121,232,233],{"class":131},"  --cert=certificate.pem",[121,235,145],{"class":144},[121,237,238],{"class":123,"line":165},[121,239,240],{"class":131},"  --key=private.key\n",[106,242,244],{"id":243},"_3-套用-k8s-設定","3. 套用 K8s 設定",[111,246,248],{"className":113,"code":247,"language":115,"meta":116,"style":116},"kubectl apply -f k8s\u002Fbackend-config.yaml\nkubectl apply -f k8s\u002Fapi-deployment.yaml\nkubectl apply -f k8s\u002Fdashboard-deployment.yaml\nkubectl apply -f k8s\u002Fweb-deployment.yaml\nkubectl apply -f k8s\u002Fingress.yaml\n",[118,249,250,263,274,285,296],{"__ignoreMap":116},[121,251,252,254,257,260],{"class":123,"line":124},[121,253,128],{"class":127},[121,255,256],{"class":131}," apply",[121,258,259],{"class":131}," -f",[121,261,262],{"class":131}," k8s\u002Fbackend-config.yaml\n",[121,264,265,267,269,271],{"class":123,"line":148},[121,266,128],{"class":127},[121,268,256],{"class":131},[121,270,259],{"class":131},[121,272,273],{"class":131}," k8s\u002Fapi-deployment.yaml\n",[121,275,276,278,280,282],{"class":123,"line":165},[121,277,128],{"class":127},[121,279,256],{"class":131},[121,281,259],{"class":131},[121,283,284],{"class":131}," k8s\u002Fdashboard-deployment.yaml\n",[121,286,287,289,291,293],{"class":123,"line":179},[121,288,128],{"class":127},[121,290,256],{"class":131},[121,292,259],{"class":131},[121,294,295],{"class":131}," k8s\u002Fweb-deployment.yaml\n",[121,297,298,300,302,304],{"class":123,"line":193},[121,299,128],{"class":127},[121,301,256],{"class":131},[121,303,259],{"class":131},[121,305,306],{"class":131}," k8s\u002Fingress.yaml\n",[101,308,310],{"id":309},"日常更新cicd-自動執行","日常更新（CI\u002FCD 自動執行）",[312,313,314],"p",{},"三個 repo 都設有 GitHub Actions，push 到 main 自動完成：",[111,316,321],{"className":317,"code":319,"language":320},[318],"language-text","push main → GitHub Actions → docker build → push Artifact Registry → kubectl rollout restart\n","text",[118,322,319],{"__ignoreMap":116},[312,324,325],{},"不需要手動 build 或部署。",[95,327],{},[44,329,330],{"id":330},"遇到的狀況與解決",[101,332,334],{"id":333},"_1-arm64amd64-平台不符","1. arm64\u002Famd64 平台不符",[48,336,337,343],{},[51,338,339,342],{},[54,340,341],{},"問題","：本地 Apple Silicon Mac build 出 arm64 image，GKE 需要 amd64",[51,344,345,348,349,352],{},[54,346,347],{},"解法","：改用 ",[118,350,351],{},"gcloud builds submit","，Cloud Build 在 amd64 機器上 build",[101,354,356],{"id":355},"_2-imagepullbackoff沒權限拉-image","2. ImagePullBackOff（沒權限拉 image）",[48,358,359,364],{},[51,360,361,363],{},[54,362,341],{},"：GKE Service Account 沒有 Artifact Registry 讀取權限",[51,365,366,368,369],{},[54,367,347],{},"：\n",[111,370,372],{"className":113,"code":371,"language":115,"meta":116,"style":116},"gcloud projects add-iam-policy-binding gshop-497319 \\\n  --member=\"serviceAccount:620172615694-compute@developer.gserviceaccount.com\" \\\n  --role=\"roles\u002Fartifactregistry.reader\"\n",[118,373,374,390,404],{"__ignoreMap":116},[121,375,376,379,382,385,388],{"class":123,"line":124},[121,377,378],{"class":127},"gcloud",[121,380,381],{"class":131}," projects",[121,383,384],{"class":131}," add-iam-policy-binding",[121,386,387],{"class":131}," gshop-497319",[121,389,145],{"class":144},[121,391,392,395,397,400,402],{"class":123,"line":148},[121,393,394],{"class":131},"  --member=",[121,396,155],{"class":154},[121,398,399],{"class":131},"serviceAccount:620172615694-compute@developer.gserviceaccount.com",[121,401,155],{"class":154},[121,403,145],{"class":144},[121,405,406,409,411,414],{"class":123,"line":165},[121,407,408],{"class":131},"  --role=",[121,410,155],{"class":154},[121,412,413],{"class":131},"roles\u002Fartifactregistry.reader",[121,415,203],{"class":154},[101,417,419],{"id":418},"_3-ingress-遲遲拿不到-ipneg-not-ready","3. Ingress 遲遲拿不到 IP（NEG not ready）",[48,421,422,432,471,481],{},[51,423,424,427,428,431],{},[54,425,426],{},"問題 1","：用了 ",[118,429,430],{},"spec.ingressClassName: gce","，GKE 的 controller 不認這個，要用 annotation",[51,433,434,436,437],{},[54,435,347],{},"：改成：\n",[111,438,442],{"className":439,"code":440,"language":441,"meta":116,"style":116},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","metadata:\n  annotations:\n    kubernetes.io\u002Fingress.class: gce\n","yaml",[118,443,444,453,460],{"__ignoreMap":116},[121,445,446,450],{"class":123,"line":124},[121,447,449],{"class":448},"swJcz","metadata",[121,451,452],{"class":154},":\n",[121,454,455,458],{"class":123,"line":148},[121,456,457],{"class":448},"  annotations",[121,459,452],{"class":154},[121,461,462,465,468],{"class":123,"line":165},[121,463,464],{"class":448},"    kubernetes.io\u002Fingress.class",[121,466,467],{"class":154},":",[121,469,470],{"class":131}," gce\n",[51,472,473,476,477,480],{},[54,474,475],{},"問題 2","：Service 上有舊的 ",[118,478,479],{},"networking.gke.io\u002Ftarget-pool"," annotation 造成衝突",[51,482,483,368,485,532],{},[54,484,347],{},[111,486,488],{"className":113,"code":487,"language":115,"meta":116,"style":116},"kubectl annotate svc gshop-api networking.gke.io\u002Ftarget-pool-\nkubectl annotate svc gshop-dashboard networking.gke.io\u002Ftarget-pool-\nkubectl annotate svc gshop-web networking.gke.io\u002Ftarget-pool-\n",[118,489,490,506,519],{"__ignoreMap":116},[121,491,492,494,497,500,503],{"class":123,"line":124},[121,493,128],{"class":127},[121,495,496],{"class":131}," annotate",[121,498,499],{"class":131}," svc",[121,501,502],{"class":131}," gshop-api",[121,504,505],{"class":131}," networking.gke.io\u002Ftarget-pool-\n",[121,507,508,510,512,514,517],{"class":123,"line":148},[121,509,128],{"class":127},[121,511,496],{"class":131},[121,513,499],{"class":131},[121,515,516],{"class":131}," gshop-dashboard",[121,518,505],{"class":131},[121,520,521,523,525,527,530],{"class":123,"line":165},[121,522,128],{"class":127},[121,524,496],{"class":131},[121,526,499],{"class":131},[121,528,529],{"class":131}," gshop-web",[121,531,505],{"class":131},"\n再刪掉重建 Ingress",[101,534,536],{"id":535},"_4-502-bad-gateway健康檢查失敗","4. 502 Bad Gateway（健康檢查失敗）",[48,538,539,572],{},[51,540,541,543,544,547,548],{},[54,542,341],{},"：GCP Load Balancer 預設用 ",[118,545,546],{},"GET \u002F"," 做健康檢查，但各服務行為不同：\n",[48,549,550,557,566],{},[51,551,552,553,556],{},"gshop-api：",[118,554,555],{},"\u002F"," 沒有 route，回非 200",[51,558,559,560,562,563],{},"gshop-dashboard：",[118,561,555],{}," 302 redirect 到 ",[118,564,565],{},"\u002Flogin",[51,567,568,569,571],{},"gshop-web：",[118,570,555],{}," 正常回 200（不需要處理）",[51,573,574,576,577,580,581,597,598],{},[54,575,347],{},"：建 ",[118,578,579],{},"BackendConfig"," 指定正確的健康檢查路徑：\n",[111,582,584],{"className":439,"code":583,"language":441,"meta":116,"style":116},"# gshop-api → \u002Fhealth\n# gshop-dashboard → \u002Flogin\n",[118,585,586,592],{"__ignoreMap":116},[121,587,588],{"class":123,"line":124},[121,589,591],{"class":590},"sHwdD","# gshop-api → \u002Fhealth\n",[121,593,594],{"class":123,"line":148},[121,595,596],{"class":590},"# gshop-dashboard → \u002Flogin\n","\n並在 Service 加 annotation：\n",[111,599,601],{"className":439,"code":600,"language":441,"meta":116,"style":116},"cloud.google.com\u002Fbackend-config: '{\"default\": \"gshop-api-backend-config\"}'\n",[118,602,603],{"__ignoreMap":116},[121,604,605,608,610,613,616],{"class":123,"line":124},[121,606,607],{"class":448},"cloud.google.com\u002Fbackend-config",[121,609,467],{"class":154},[121,611,612],{"class":154}," '",[121,614,615],{"class":131},"{\"default\": \"gshop-api-backend-config\"}",[121,617,618],{"class":154},"'\n",[101,620,622],{"id":621},"_5-supabase-連線失敗enotfound","5. Supabase 連線失敗（ENOTFOUND）",[48,624,625,634],{},[51,626,627,629,630,633],{},[54,628,341],{},"：Supabase 直連（",[118,631,632],{},"db.xxx.supabase.co:5432","）是 IPv6 only，GKE 是 IPv4 only",[51,635,636,638,639,642,643,649,650,754],{},[54,637,347],{},"：改用 Supabase ",[54,640,641],{},"Session Pooler"," connection string：\n",[111,644,647],{"className":645,"code":646,"language":320},[318],"postgresql:\u002F\u002Fpostgres.vqjzzlutlovqoqshwbza:PASSWORD@aws-1-ap-southeast-1.pooler.supabase.com:5432\u002Fpostgres\n",[118,648,646],{"__ignoreMap":116},"\n更新 K8s secret：\n",[111,651,653],{"className":113,"code":652,"language":115,"meta":116,"style":116},"NEW_URL=\"postgresql:\u002F\u002F...\"\nkubectl patch secret gshop-api-secret -p \"{\\\"data\\\":{\\\"DATABASE_URL\\\":\\\"$(echo -n $NEW_URL | base64)\\\"}}\"\nkubectl rollout restart deployment\u002Fgshop-api\n",[118,654,655,670,741],{"__ignoreMap":116},[121,656,657,660,663,665,668],{"class":123,"line":124},[121,658,659],{"class":144},"NEW_URL",[121,661,662],{"class":154},"=",[121,664,155],{"class":154},[121,666,667],{"class":131},"postgresql:\u002F\u002F...",[121,669,203],{"class":154},[121,671,672,674,677,679,681,684,687,690,693,696,698,701,703,706,708,710,712,715,719,722,725,728,731,734,736,739],{"class":123,"line":148},[121,673,128],{"class":127},[121,675,676],{"class":131}," patch",[121,678,135],{"class":131},[121,680,141],{"class":131},[121,682,683],{"class":131}," -p",[121,685,686],{"class":154}," \"",[121,688,689],{"class":131},"{",[121,691,692],{"class":144},"\\\"",[121,694,695],{"class":131},"data",[121,697,692],{"class":144},[121,699,700],{"class":131},":{",[121,702,692],{"class":144},[121,704,705],{"class":131},"DATABASE_URL",[121,707,692],{"class":144},[121,709,467],{"class":131},[121,711,692],{"class":144},[121,713,714],{"class":154},"$(",[121,716,718],{"class":717},"s2Zo4","echo",[121,720,721],{"class":131}," -n ",[121,723,724],{"class":144},"$NEW_URL",[121,726,727],{"class":154}," |",[121,729,730],{"class":127}," base64",[121,732,733],{"class":154},")",[121,735,692],{"class":144},[121,737,738],{"class":131},"}}",[121,740,203],{"class":154},[121,742,743,745,748,751],{"class":123,"line":165},[121,744,128],{"class":127},[121,746,747],{"class":131}," rollout",[121,749,750],{"class":131}," restart",[121,752,753],{"class":131}," deployment\u002Fgshop-api\n",[755,756,757],"blockquote",{},[312,758,759],{},"本地開發不需要改，Mac 支援 IPv6 直連沒問題",[95,761],{},[44,763,765],{"id":764},"dns-https-設定","DNS & HTTPS 設定",[101,767,769],{"id":768},"cloudflare-a-records","Cloudflare A Records",[771,772,773,786],"table",{},[774,775,776],"thead",{},[777,778,779,783],"tr",{},[780,781,782],"th",{},"子網域",[780,784,785],{},"IP",[787,788,789,798,805],"tbody",{},[777,790,791,795],{},[792,793,794],"td",{},"api.garydemo.com",[792,796,797],{},"34.160.168.110",[777,799,800,803],{},[792,801,802],{},"dashboard.garydemo.com",[792,804,797],{},[777,806,807,810],{},[792,808,809],{},"web.garydemo.com",[792,811,797],{},[755,813,814],{},[312,815,816],{},"三個都指向同一個 Ingress IP，由 Ingress 依 Host header 分流",[101,818,820],{"id":819},"cloudflare-ssltls-模式","Cloudflare SSL\u002FTLS 模式",[48,822,823,829],{},[51,824,825,826],{},"設為 ",[54,827,828],{},"Full (Strict)",[51,830,831],{},"使用 Cloudflare Origin Certificate 存為 K8s TLS Secret",[101,833,834],{"id":834},"流量路徑",[111,836,839],{"className":837,"code":838,"language":320},[318],"瀏覽器 → Cloudflare（Proxy + TLS）→ GCP Load Balancer → Ingress → Pod\n",[118,840,838],{"__ignoreMap":116},[95,842],{},[44,844,846],{"id":845},"cicdgithub-actions","CI\u002FCD（GitHub Actions）",[312,848,849],{},"每個 service 各自有獨立 repo，push 到 main 自動 build + deploy。",[111,851,854],{"className":852,"code":853,"language":320},[318],"push main → GitHub Actions → build image → push Artifact Registry → kubectl rollout restart\n",[118,855,853],{"__ignoreMap":116},[101,857,859],{"id":858},"前置設定一次性","前置設定（一次性）",[111,861,863],{"className":113,"code":862,"language":115,"meta":116,"style":116},"gcloud iam service-accounts create github-actions \\\n  --display-name=\"GitHub Actions\"\n\ngcloud projects add-iam-policy-binding gshop-497319 \\\n  --member=\"serviceAccount:github-actions@gshop-497319.iam.gserviceaccount.com\" \\\n  --role=\"roles\u002Fartifactregistry.writer\"\n\ngcloud projects add-iam-policy-binding gshop-497319 \\\n  --member=\"serviceAccount:github-actions@gshop-497319.iam.gserviceaccount.com\" \\\n  --role=\"roles\u002Fcontainer.developer\"\n\ngcloud iam service-accounts keys create sa-key.json \\\n  --iam-account=github-actions@gshop-497319.iam.gserviceaccount.com\n",[118,864,865,882,894,900,912,925,937,942,955,968,980,985,1004],{"__ignoreMap":116},[121,866,867,869,872,875,877,880],{"class":123,"line":124},[121,868,378],{"class":127},[121,870,871],{"class":131}," iam",[121,873,874],{"class":131}," service-accounts",[121,876,132],{"class":131},[121,878,879],{"class":131}," github-actions",[121,881,145],{"class":144},[121,883,884,887,889,892],{"class":123,"line":148},[121,885,886],{"class":131},"  --display-name=",[121,888,155],{"class":154},[121,890,891],{"class":131},"GitHub Actions",[121,893,203],{"class":154},[121,895,896],{"class":123,"line":165},[121,897,899],{"emptyLinePlaceholder":898},true,"\n",[121,901,902,904,906,908,910],{"class":123,"line":179},[121,903,378],{"class":127},[121,905,381],{"class":131},[121,907,384],{"class":131},[121,909,387],{"class":131},[121,911,145],{"class":144},[121,913,914,916,918,921,923],{"class":123,"line":193},[121,915,394],{"class":131},[121,917,155],{"class":154},[121,919,920],{"class":131},"serviceAccount:github-actions@gshop-497319.iam.gserviceaccount.com",[121,922,155],{"class":154},[121,924,145],{"class":144},[121,926,928,930,932,935],{"class":123,"line":927},6,[121,929,408],{"class":131},[121,931,155],{"class":154},[121,933,934],{"class":131},"roles\u002Fartifactregistry.writer",[121,936,203],{"class":154},[121,938,940],{"class":123,"line":939},7,[121,941,899],{"emptyLinePlaceholder":898},[121,943,945,947,949,951,953],{"class":123,"line":944},8,[121,946,378],{"class":127},[121,948,381],{"class":131},[121,950,384],{"class":131},[121,952,387],{"class":131},[121,954,145],{"class":144},[121,956,958,960,962,964,966],{"class":123,"line":957},9,[121,959,394],{"class":131},[121,961,155],{"class":154},[121,963,920],{"class":131},[121,965,155],{"class":154},[121,967,145],{"class":144},[121,969,971,973,975,978],{"class":123,"line":970},10,[121,972,408],{"class":131},[121,974,155],{"class":154},[121,976,977],{"class":131},"roles\u002Fcontainer.developer",[121,979,203],{"class":154},[121,981,983],{"class":123,"line":982},11,[121,984,899],{"emptyLinePlaceholder":898},[121,986,988,990,992,994,997,999,1002],{"class":123,"line":987},12,[121,989,378],{"class":127},[121,991,871],{"class":131},[121,993,874],{"class":131},[121,995,996],{"class":131}," keys",[121,998,132],{"class":131},[121,1000,1001],{"class":131}," sa-key.json",[121,1003,145],{"class":144},[121,1005,1007],{"class":123,"line":1006},13,[121,1008,1009],{"class":131},"  --iam-account=github-actions@gshop-497319.iam.gserviceaccount.com\n",[312,1011,1012,1013,1016,1017,1020],{},"GitHub org → Settings → Secrets → New organization secret，名稱 ",[118,1014,1015],{},"GCP_SA_KEY","，貼入 ",[118,1018,1019],{},"sa-key.json"," 內容。",[101,1022,1024],{"id":1023},"workflow-範例","Workflow 範例",[111,1026,1028],{"className":439,"code":1027,"language":441,"meta":116,"style":116},"name: Deploy gshop-api\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: google-github-actions\u002Fauth@v2\n        with:\n          credentials_json: ${{ secrets.GCP_SA_KEY }}\n      - uses: google-github-actions\u002Fsetup-gcloud@v2\n      - run: gcloud auth configure-docker asia-east1-docker.pkg.dev\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      - uses: google-github-actions\u002Fget-gke-credentials@v2\n        with:\n          cluster_name: gshop-cluster\n          location: asia-east1\n      - run: kubectl rollout restart deployment\u002Fgshop-api\n",[118,1029,1030,1040,1044,1052,1059,1066,1074,1078,1085,1092,1102,1109,1121,1132,1140,1151,1163,1176,1189,1201,1207,1213,1225,1232,1243,1254],{"__ignoreMap":116},[121,1031,1032,1035,1037],{"class":123,"line":124},[121,1033,1034],{"class":448},"name",[121,1036,467],{"class":154},[121,1038,1039],{"class":131}," Deploy gshop-api\n",[121,1041,1042],{"class":123,"line":148},[121,1043,899],{"emptyLinePlaceholder":898},[121,1045,1046,1050],{"class":123,"line":165},[121,1047,1049],{"class":1048},"sfNiH","on",[121,1051,452],{"class":154},[121,1053,1054,1057],{"class":123,"line":179},[121,1055,1056],{"class":448},"  push",[121,1058,452],{"class":154},[121,1060,1061,1064],{"class":123,"line":193},[121,1062,1063],{"class":448},"    branches",[121,1065,452],{"class":154},[121,1067,1068,1071],{"class":123,"line":927},[121,1069,1070],{"class":154},"      -",[121,1072,1073],{"class":131}," main\n",[121,1075,1076],{"class":123,"line":939},[121,1077,899],{"emptyLinePlaceholder":898},[121,1079,1080,1083],{"class":123,"line":944},[121,1081,1082],{"class":448},"jobs",[121,1084,452],{"class":154},[121,1086,1087,1090],{"class":123,"line":957},[121,1088,1089],{"class":448},"  deploy",[121,1091,452],{"class":154},[121,1093,1094,1097,1099],{"class":123,"line":970},[121,1095,1096],{"class":448},"    runs-on",[121,1098,467],{"class":154},[121,1100,1101],{"class":131}," ubuntu-latest\n",[121,1103,1104,1107],{"class":123,"line":982},[121,1105,1106],{"class":448},"    steps",[121,1108,452],{"class":154},[121,1110,1111,1113,1116,1118],{"class":123,"line":987},[121,1112,1070],{"class":154},[121,1114,1115],{"class":448}," uses",[121,1117,467],{"class":154},[121,1119,1120],{"class":131}," actions\u002Fcheckout@v4\n",[121,1122,1123,1125,1127,1129],{"class":123,"line":1006},[121,1124,1070],{"class":154},[121,1126,1115],{"class":448},[121,1128,467],{"class":154},[121,1130,1131],{"class":131}," google-github-actions\u002Fauth@v2\n",[121,1133,1135,1138],{"class":123,"line":1134},14,[121,1136,1137],{"class":448},"        with",[121,1139,452],{"class":154},[121,1141,1143,1146,1148],{"class":123,"line":1142},15,[121,1144,1145],{"class":448},"          credentials_json",[121,1147,467],{"class":154},[121,1149,1150],{"class":131}," ${{ secrets.GCP_SA_KEY }}\n",[121,1152,1154,1156,1158,1160],{"class":123,"line":1153},16,[121,1155,1070],{"class":154},[121,1157,1115],{"class":448},[121,1159,467],{"class":154},[121,1161,1162],{"class":131}," google-github-actions\u002Fsetup-gcloud@v2\n",[121,1164,1166,1168,1171,1173],{"class":123,"line":1165},17,[121,1167,1070],{"class":154},[121,1169,1170],{"class":448}," run",[121,1172,467],{"class":154},[121,1174,1175],{"class":131}," gcloud auth configure-docker asia-east1-docker.pkg.dev\n",[121,1177,1179,1181,1184,1186],{"class":123,"line":1178},18,[121,1180,1070],{"class":154},[121,1182,1183],{"class":448}," name",[121,1185,467],{"class":154},[121,1187,1188],{"class":131}," Build and push image\n",[121,1190,1192,1195,1197],{"class":123,"line":1191},19,[121,1193,1194],{"class":448},"        run",[121,1196,467],{"class":154},[121,1198,1200],{"class":1199},"s7zQu"," |\n",[121,1202,1204],{"class":123,"line":1203},20,[121,1205,1206],{"class":131},"          docker build -t asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fapi:latest .\n",[121,1208,1210],{"class":123,"line":1209},21,[121,1211,1212],{"class":131},"          docker push asia-east1-docker.pkg.dev\u002Fgshop-497319\u002Fgshop\u002Fapi:latest\n",[121,1214,1216,1218,1220,1222],{"class":123,"line":1215},22,[121,1217,1070],{"class":154},[121,1219,1115],{"class":448},[121,1221,467],{"class":154},[121,1223,1224],{"class":131}," google-github-actions\u002Fget-gke-credentials@v2\n",[121,1226,1228,1230],{"class":123,"line":1227},23,[121,1229,1137],{"class":448},[121,1231,452],{"class":154},[121,1233,1235,1238,1240],{"class":123,"line":1234},24,[121,1236,1237],{"class":448},"          cluster_name",[121,1239,467],{"class":154},[121,1241,1242],{"class":131}," gshop-cluster\n",[121,1244,1246,1249,1251],{"class":123,"line":1245},25,[121,1247,1248],{"class":448},"          location",[121,1250,467],{"class":154},[121,1252,1253],{"class":131}," asia-east1\n",[121,1255,1257,1259,1261,1263],{"class":123,"line":1256},26,[121,1258,1070],{"class":154},[121,1260,1170],{"class":448},[121,1262,467],{"class":154},[121,1264,1265],{"class":131}," kubectl rollout restart deployment\u002Fgshop-api\n",[755,1267,1268],{},[312,1269,1270],{},"GitHub Actions runner 是 ubuntu amd64，build 出的 image 天生就是 amd64，不需要 Cloud Build",[95,1272],{},[44,1274,1275],{"id":1275},"常用指令",[111,1277,1279],{"className":113,"code":1278,"language":115,"meta":116,"style":116},"# 查 pod 狀態\nkubectl get pods\n\n# 查 ingress IP\nkubectl get ingress gshop-ingress\n\n# 查某服務 log\nkubectl logs -l app=gshop-api --tail=50\n\n# 查 backend 健康狀態\ngcloud compute backend-services get-health \u003Cbackend-name> --global\n\n# 列出所有 backend\ngcloud compute backend-services list --global\n\n# 查 NEG\ngcloud compute network-endpoint-groups list\n",[118,1280,1281,1286,1296,1300,1305,1317,1321,1326,1342,1346,1351,1379,1383,1388,1401,1405,1410],{"__ignoreMap":116},[121,1282,1283],{"class":123,"line":124},[121,1284,1285],{"class":590},"# 查 pod 狀態\n",[121,1287,1288,1290,1293],{"class":123,"line":148},[121,1289,128],{"class":127},[121,1291,1292],{"class":131}," get",[121,1294,1295],{"class":131}," pods\n",[121,1297,1298],{"class":123,"line":165},[121,1299,899],{"emptyLinePlaceholder":898},[121,1301,1302],{"class":123,"line":179},[121,1303,1304],{"class":590},"# 查 ingress IP\n",[121,1306,1307,1309,1311,1314],{"class":123,"line":193},[121,1308,128],{"class":127},[121,1310,1292],{"class":131},[121,1312,1313],{"class":131}," ingress",[121,1315,1316],{"class":131}," gshop-ingress\n",[121,1318,1319],{"class":123,"line":927},[121,1320,899],{"emptyLinePlaceholder":898},[121,1322,1323],{"class":123,"line":939},[121,1324,1325],{"class":590},"# 查某服務 log\n",[121,1327,1328,1330,1333,1336,1339],{"class":123,"line":944},[121,1329,128],{"class":127},[121,1331,1332],{"class":131}," logs",[121,1334,1335],{"class":131}," -l",[121,1337,1338],{"class":131}," app=gshop-api",[121,1340,1341],{"class":131}," --tail=50\n",[121,1343,1344],{"class":123,"line":957},[121,1345,899],{"emptyLinePlaceholder":898},[121,1347,1348],{"class":123,"line":970},[121,1349,1350],{"class":590},"# 查 backend 健康狀態\n",[121,1352,1353,1355,1358,1361,1364,1367,1370,1373,1376],{"class":123,"line":982},[121,1354,378],{"class":127},[121,1356,1357],{"class":131}," compute",[121,1359,1360],{"class":131}," backend-services",[121,1362,1363],{"class":131}," get-health",[121,1365,1366],{"class":154}," \u003C",[121,1368,1369],{"class":131},"backend-nam",[121,1371,1372],{"class":144},"e",[121,1374,1375],{"class":154},">",[121,1377,1378],{"class":131}," --global\n",[121,1380,1381],{"class":123,"line":987},[121,1382,899],{"emptyLinePlaceholder":898},[121,1384,1385],{"class":123,"line":1006},[121,1386,1387],{"class":590},"# 列出所有 backend\n",[121,1389,1390,1392,1394,1396,1399],{"class":123,"line":1134},[121,1391,378],{"class":127},[121,1393,1357],{"class":131},[121,1395,1360],{"class":131},[121,1397,1398],{"class":131}," list",[121,1400,1378],{"class":131},[121,1402,1403],{"class":123,"line":1142},[121,1404,899],{"emptyLinePlaceholder":898},[121,1406,1407],{"class":123,"line":1153},[121,1408,1409],{"class":590},"# 查 NEG\n",[121,1411,1412,1414,1416,1419],{"class":123,"line":1165},[121,1413,378],{"class":127},[121,1415,1357],{"class":131},[121,1417,1418],{"class":131}," network-endpoint-groups",[121,1420,1421],{"class":131}," list\n",[1423,1424,1425],"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 pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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":116,"searchDepth":148,"depth":148,"links":1427},[1428,1429,1433,1440,1445,1449],{"id":46,"depth":148,"text":46},{"id":99,"depth":148,"text":99,"children":1430},[1431,1432],{"id":103,"depth":165,"text":104},{"id":309,"depth":165,"text":310},{"id":330,"depth":148,"text":330,"children":1434},[1435,1436,1437,1438,1439],{"id":333,"depth":165,"text":334},{"id":355,"depth":165,"text":356},{"id":418,"depth":165,"text":419},{"id":535,"depth":165,"text":536},{"id":621,"depth":165,"text":622},{"id":764,"depth":148,"text":765,"children":1441},[1442,1443,1444],{"id":768,"depth":165,"text":769},{"id":819,"depth":165,"text":820},{"id":834,"depth":165,"text":834},{"id":845,"depth":148,"text":846,"children":1446},[1447,1448],{"id":858,"depth":165,"text":859},{"id":1023,"depth":165,"text":1024},{"id":1275,"depth":148,"text":1275},"2026-06-16","記錄將個人電商（GShop）部署到 GKE Autopilot 的完整流程。","md",null,"\u002Fimages\u002Fgke.jpg",{},{"title":14,"description":1451},"0WXx7GmLgxmdtZM5CXX1oRaqKSqXhVwlR1hreTnFxdY",[1459,1461],{"title":10,"path":11,"stem":12,"description":1460,"children":-1},"資料量龐大導致查詢變慢，透過 Cron Job 將每日統計好的資料寫入 Snapshot Table，查詢不再需要跨表掃描改為單表讀取。",{"title":18,"path":19,"stem":20,"description":1462,"children":-1},"從 Token、Context、Prompt，到 Tool、MCP、Agent，完整梳理 AI 應用開發的核心概念與底層運作原理。",1782321732707]