[Cloud Build 教學] 用 Cloud Build 部署 Cloud Run 存取 GCS 才不會碰到權限問題

我想試試看 nginx 在 Cloud Run 啟動之後,

能不能去 Cloud Storage 讀取最新的 index.html 檔案:

這樣的話,我以後每次改網頁,我就不用重新建立 Docker Image,

或是重新跑一個 CI/CD 的流程。

我先用預設的 Nginx Container,

而我在 Cloud Storage 寫的首頁 index.html 是 “Hello world from Dongdong Cloud Storage!!”

我希望部署出去後,就顯示這個首頁,代表這個檔案是部署的當下去 GCS 抓過來的。

但是我如果在 Cloud Shell 使用 docker build 指令,

Docker 執行 gstuil cp 去 Cloud Storage 複製檔案時,

總是會碰到這個錯誤:

ServiceException: 401 Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object. Permission ‘storage.objects.get’ denied on resource (or it may not exist)

就是 GCP 並不認識 Dockerfile 執行的指令,把它視為匿名者 Anonymous。

我已經試了應該有2個禮拜了,一直都沒有成功,

現在打算改用 Cloud Build 來試試看。

因為 Cloud Build 會使用預設的 Service Account,

這樣 Cloud Storage 會認得是它去拉資料的。

像這個是我專案的預設 Service Account:

600195576280-compute@developer.gserviceaccount.com

它本身已經具有一些權限,因為它預設就是 Editor 的角色。

這個 Service Account 除了操作 Cloud Build 之外,

會把建置過程的 Log 寫到 Cloud Storage 的某個地方,

還要把 Image Push 到 Arfitact Registry,

所以如果要另外使用 Service Account,相關的權限和 Cloud Storage Bucket 都要建立起來。

在此我們一樣先用預設的 Service Account 就好。

我先在 Cloud Shell 建立資料夾

mkdir cloud-run

cd cloud-run

mkdir nginx-cp-gcs

cd nginx-cp-gcs

建立相關檔案

首先是 Web Server Nginx 的設定檔,

只是用預設的,讓它對外使用 8080 Port 即可。

vim nginx.conf

server {
    listen 8080;
    server_name localhost;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }
}

再來是 Dockerfile,

建立 Image 時要執行的一系列動作會在這裡準備好。

vim Dockerfile

# 使用官方的 Google Cloud SDK 映像作為基礎
FROM gcr.io/google.com/cloudsdktool/cloud-sdk:alpine

# 安裝 Nginx
RUN apk add --no-cache nginx

# 複製 Nginx 配置
COPY nginx.conf /etc/nginx/http.d/default.conf

# 複製啟動腳本
COPY start.sh /start.sh
RUN chmod +x /start.sh

# 創建 Nginx 運行所需的目錄
RUN mkdir -p /run/nginx

# 設置工作目錄
WORKDIR /usr/share/nginx/html

# 暴露 8080 端口
EXPOSE 8080

# 設置入口點
ENTRYPOINT ["/start.sh"]

你會注意到它在最後有一個入口點,

它會執行 start.sh 這個檔案:

vim start.sh

#!/bin/sh

# 從 GCS 下載 index.html
gsutil cp gs://dong-dong-gcp-config/index.html /usr/share/nginx/html/index.html

# 啟動 Nginx
nginx -g 'daemon off;'

你可能會好奇,為什麼要另外執行 gsutil cp 多此一舉?

因為獨立的 start.sh 讓我們在容器啟動後執行動態操作,

比如從 GCS 下載最新的 index.html

如果我們將這個操作放在 Dockerfile 中,

它只會在構建時執行一次,而不是每次容器啟動時都執行。

所以另外使用 start.sh 就可以讓整個流程更為靈活。

最後我們是要呼叫 Cloud Build 來幫我們執行建立 Image、Pull Image 和部署到 Cloud Run 的動作,

我們就全部都寫到 cloudbuild.yaml 這個檔案裡:

vim cloudbuild.yaml

steps:
# 建立 Docker 映像檔
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'asia-east1-docker.pkg.dev/dong-dong-gcp-3/nginx/cp-index-from-gcs', '.']

# 推送 Image 到 Artifact Registry
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'asia-east1-docker.pkg.dev/dong-dong-gcp-3/nginx/cp-index-from-gcs']

# 部署到 Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  entrypoint: 'gcloud'
  args:
  - 'run'
  - 'deploy'
  - 'nginx-custom-service'
  - '--image'
  - 'asia-east1-docker.pkg.dev/dong-dong-gcp-3/nginx/cp-index-from-gcs'
  - '--region'
  - 'asia-east1'
  - '--platform'
  - 'managed'
  - '--service-account'
  - 'cp-from-gcs@dong-dong-gcp-3.iam.gserviceaccount.com'
  - '--allow-unauthenticated'

images:
- 'asia-east1-docker.pkg.dev/dong-dong-gcp-3/nginx/cp-index-from-gcs'

我們現在總共有這些檔案:

最後就來部署了!

把所有動作都照 cloudbuild.yaml 裡的指令來執行即可:

gcloud builds submit –config=cloudbuild.yaml

接下來要等3分鐘左右,讓它把所有步驟跑完。

在等待的同時,也可以去 Cloud Build 的 Console 看看有什麼東西

你可以點擊最新的版本,

會看到所有建置過程的記錄:

你會看到三個步驟:docker build、docker push、gcloud run deploy

都有詳細記錄建置的過程。

最後顯示部署完成

那我們去 Cloud Run Console 看看

再點進去看看

會你看到它的網站竟然是反灰的,

因為「調整權限」這個動作,Cloud Build 的 Service Account 是沒有權限的,

我們要人工執行下方這個「調整權限」的指令,才會有公開 URL

gcloud run services add-iam-policy-binding nginx-custom-service \
  --region=asia-east1 \
  --member=allUsers \
  --role=roles/run.invoker

我們再重新整理一下網頁:

成功了,看到的網頁是從 Cloud Storage 複製過來的。

所以結論就是,

不要在 Dockerfile 這個「小三」裡面做太多事,

尤其是要呼叫 GCP 各項 API 的時候,

讓 Cloud Build 這個「有名份」的老婆去 Cloud Storage 拿東西,

就不會被警衛阻止了。

說實話,我並不是熟悉寫程式的,

也許各位軟體工程師有更好的方法也說不定,

歡迎批評指教,謝謝大家!

Table of Contents
返回頂端