我想通过使用 Dockerfile 构建映像并部署到 Google Cloud App Engine 来部署 python hello-world 服务。这些是我用于此目的的文件:
# app.py
#!/usr/bin/python
import time
from flask import Flask
app = Flask(__name__)
START = time.time()
def elapsed():
running = time.time() - START
minutes, seconds = divmod(running, 60)
hours, minutes = divmod(minutes, 60)
return "%d:%02d:%02d" % (hours, minutes, seconds)
@app.route('/')
def root():
return "Hello World (Python)! (up %s)\n" % elapsed()
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=8080)
# requirements.txt
flask
# Dockerfile
FROM python:3-alpine
WORKDIR /service
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . ./
EXPOSE 8080
ENTRYPOINT ["python3", "app.py"]
# app. yaml
runtime: custom
env: flex
service: py-hello
使用
docker build
和 docker run
命令运行此代码时会成功运行。
将此应用程序部署到 Google App Engine 时,我收到错误:
$ gcloud app deploy
Services to deploy:
descriptor: [/Users/carlostomas/Workspace/gae-py-hello/app.yaml]
source: [/Users/carlostomas/Workspace/gae-py-hello]
target project: [intick-rfq-dev]
target service: [py-hello]
target version: [20240623t113720]
target url: [https://py-hello-dot-intick-rfq-dev.nw.r.appspot.com]
target service account: [[email protected]]
Do you want to continue (Y/n)? y
Beginning deployment of service [py-hello]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 4 files to Google Cloud Storage ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [py-hello] (this may take several minutes)...done.
Waiting for build to complete. Polling interval: 1 second(s).
------------------------------------------------------------------------- REMOTE BUILD OUTPUT --------------------------------------------------------------------------
starting build "68970ce5-9ea1-4a8b-ad20-43b97eccda25"
FETCHSOURCE
BUILD
Starting Step #0 - "fetcher"
Step #0 - "fetcher": Already have image (with digest): gcr.io/cloud-builders/gcs-fetcher
Step #0 - "fetcher": Fetching manifest gs://staging.intick-rfq-dev.appspot.com/ae/3e87d960-7d7d-4a44-a7bf-1b0af0eac2bb/manifest.json.
Step #0 - "fetcher": Processing 4 files.
Step #0 - "fetcher": ******************************************************
Step #0 - "fetcher": Status: SUCCESS
Step #0 - "fetcher": Started: 2024-06-23T09:37:32Z
Step #0 - "fetcher": Completed: 2024-06-23T09:37:34Z
Step #0 - "fetcher": Requested workers: 200
Step #0 - "fetcher": Actual workers: 4
Step #0 - "fetcher": Total files: 4
Step #0 - "fetcher": Total retries: 0
Step #0 - "fetcher": GCS timeouts: 0
Step #0 - "fetcher": MiB downloaded: 0.00 MiB
Step #0 - "fetcher": MiB/s throughput: 0.00 MiB/s
Step #0 - "fetcher": Time for manifest: 950.67 ms
Step #0 - "fetcher": Total time: 1.88 s
Step #0 - "fetcher": ******************************************************
Finished Step #0 - "fetcher"
Starting Step #1
Step #1: Already have image (with digest): gcr.io/cloud-builders/docker
Step #1: Sending build context to Docker daemon 5.12kB
Step #1: Step 1/7 : FROM python:3-alpine
Step #1: 3-alpine: Pulling from library/python
Step #1: ec99f8b99825: Pulling fs layer
Step #1: a68bf89b0030: Pulling fs layer
Step #1: dc4556e82255: Pulling fs layer
Step #1: 32e2ec03db6e: Pulling fs layer
Step #1: eb2ccc024fc3: Pulling fs layer
Step #1: eb2ccc024fc3: Waiting
Step #1: 32e2ec03db6e: Waiting
Step #1: a68bf89b0030: Verifying Checksum
Step #1: a68bf89b0030: Download complete
Step #1: ec99f8b99825: Download complete
Step #1: dc4556e82255: Verifying Checksum
Step #1: 32e2ec03db6e: Verifying Checksum
Step #1: 32e2ec03db6e: Download complete
Step #1: dc4556e82255: Download complete
Step #1: ec99f8b99825: Pull complete
Step #1: eb2ccc024fc3: Verifying Checksum
Step #1: eb2ccc024fc3: Download complete
Step #1: a68bf89b0030: Pull complete
Step #1: dc4556e82255: Pull complete
Step #1: 32e2ec03db6e: Pull complete
Step #1: eb2ccc024fc3: Pull complete
Step #1: Digest: sha256:dc095966439c68283a01dde5e5bc9819ba24b28037dddd64ea224bf7aafc0c82
Step #1: Status: Downloaded newer image for python:3-alpine
Step #1: ---> 5d3a5c7fea1e
Step #1: Step 2/7 : WORKDIR /service
Step #1: ---> Running in 629ca24729e4
Step #1: Removing intermediate container 629ca24729e4
Step #1: ---> 749449f276aa
Step #1: Step 3/7 : COPY requirements.txt .
Step #1: ---> ebefde10db6e
Step #1: Step 4/7 : RUN pip install -r requirements.txt
Step #1: ---> Running in 0f7146aae859
Step #1: Collecting flask (from -r requirements.txt (line 1))
Step #1: Downloading flask-3.0.3-py3-none-any.whl.metadata (3.2 kB)
Step #1: Collecting Werkzeug>=3.0.0 (from flask->-r requirements.txt (line 1))
Step #1: Downloading werkzeug-3.0.3-py3-none-any.whl.metadata (3.7 kB)
Step #1: Collecting Jinja2>=3.1.2 (from flask->-r requirements.txt (line 1))
Step #1: Downloading jinja2-3.1.4-py3-none-any.whl.metadata (2.6 kB)
Step #1: Collecting itsdangerous>=2.1.2 (from flask->-r requirements.txt (line 1))
Step #1: Downloading itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)
Step #1: Collecting click>=8.1.3 (from flask->-r requirements.txt (line 1))
Step #1: Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
Step #1: Collecting blinker>=1.6.2 (from flask->-r requirements.txt (line 1))
Step #1: Downloading blinker-1.8.2-py3-none-any.whl.metadata (1.6 kB)
Step #1: Collecting MarkupSafe>=2.0 (from Jinja2>=3.1.2->flask->-r requirements.txt (line 1))
Step #1: Downloading MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl.metadata (3.0 kB)
Step #1: Downloading flask-3.0.3-py3-none-any.whl (101 kB)
Step #1: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.7/101.7 kB 5.7 MB/s eta 0:00:00
Step #1: Downloading blinker-1.8.2-py3-none-any.whl (9.5 kB)
Step #1: Downloading click-8.1.7-py3-none-any.whl (97 kB)
Step #1: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 15.2 MB/s eta 0:00:00
Step #1: Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Step #1: Downloading jinja2-3.1.4-py3-none-any.whl (133 kB)
Step #1: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.3/133.3 kB 13.2 MB/s eta 0:00:00
Step #1: Downloading werkzeug-3.0.3-py3-none-any.whl (227 kB)
Step #1: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 227.3/227.3 kB 31.8 MB/s eta 0:00:00
Step #1: Downloading MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl (33 kB)
Step #1: Installing collected packages: MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, flask
Step #1: Successfully installed Jinja2-3.1.4 MarkupSafe-2.1.5 Werkzeug-3.0.3 blinker-1.8.2 click-8.1.7 flask-3.0.3 itsdangerous-2.2.0
Step #1: WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Step #1:
Step #1: [notice] A new release of pip is available: 24.0 -> 24.1
Step #1: [notice] To update, run: pip install --upgrade pip
Step #1: Removing intermediate container 0f7146aae859
Step #1: ---> a48c5a156824
Step #1: Step 5/7 : COPY . ./
Step #1: ---> 2b409b2cf985
Step #1: Step 6/7 : EXPOSE 8080
Step #1: ---> Running in d6bfba38f90f
Step #1: Removing intermediate container d6bfba38f90f
Step #1: ---> 37d35496f2e7
Step #1: Step 7/7 : ENTRYPOINT ["python3", "app.py"]
Step #1: ---> Running in 5af754f41b39
Step #1: Removing intermediate container 5af754f41b39
Step #1: ---> d7c26a000a2b
Step #1: Successfully built d7c26a000a2b
Step #1: Successfully tagged eu.gcr.io/intick-rfq-dev/appengine/py-hello.20240623t113720:latest
Finished Step #1
PUSH
Pushing eu.gcr.io/intick-rfq-dev/appengine/py-hello.20240623t113720
The push refers to repository [eu.gcr.io/intick-rfq-dev/appengine/py-hello.20240623t113720]
0a46395f91fe: Preparing
3ed896076f1d: Preparing
e033cfdc4107: Preparing
534fdafdd34c: Preparing
da17acc3bd72: Preparing
d99d106fe90f: Preparing
407fd3c96102: Preparing
699ede46ccf2: Preparing
94e5f06ff8e3: Preparing
d99d106fe90f: Waiting
407fd3c96102: Waiting
699ede46ccf2: Waiting
94e5f06ff8e3: Waiting
da17acc3bd72: Layer already exists
d99d106fe90f: Layer already exists
407fd3c96102: Layer already exists
699ede46ccf2: Layer already exists
94e5f06ff8e3: Layer already exists
0a46395f91fe: Pushed
e033cfdc4107: Pushed
534fdafdd34c: Pushed
3ed896076f1d: Pushed
latest: digest: sha256:e3539e8bfaab427b6ef99de35ccd6f3f71f9b1b0f21698a85b528a2e08d858c2 size: 2200
DONE
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Updating service [py-hello] (this may take several minutes)...failed.
ERROR: (gcloud.app.deploy) Error Response: [7] The App Engine appspot and App Engine flexible environment service accounts must have permissions on the image [eu.gcr.io/intick-rfq-dev/appengine/py-hello.20240623t113720]. Please check that the App Engine default service account has the [Storage Object Viewer] role and the App Engine Flexible service account has the App Engine Flexible Environment Service Agent role
镜像
eu.gcr.io/intick-rfq-dev/appengine/py-hello.20240623t113720:latest
已成功拉取至Google容器注册表。我什至可以将此映像拉到本地计算机并运行 docker run
命令,该服务将在 Docker 容器中运行。
此错误仅在尝试通过构建 Dockerfile 并使用灵活环境来部署应用程序时发生 (
env: flex
)。使用标准环境且没有 Dockerfile 部署其他应用程序(nodejs、python、java)时,我没有任何问题。
这些是与 App Engine 默认和灵活环境服务帐户关联的角色:
$ gcloud projects get-iam-policy intick-rfq-dev \
--flatten="bindings[].members" \
--format='table(bindings.role)' \
--filter="bindings.members:[email protected]"
ROLE
roles/appengineflex.serviceAgent
roles/editor
roles/secretmanager.secretAccessor
roles/storage.objectViewer
$ gcloud projects get-iam-policy intick-rfq-dev \
--flatten="bindings[].members" \
--format='table(bindings.role)' \
--filter="bindings.members:service-169683775925@gae-api-prod.google.com.iam.gserviceaccount.com"
ROLE
roles/appengineflex.serviceAgent
App Engine 服务帐户似乎具有与其关联的正确角色。
根据App Engine错误排查页面: https://cloud.google.com/appengine/docs/flexible/troubleshooting#service-account-permissions
此错误有两个潜在原因:
第一个不可能是原因,因为服务帐户具有该角色。
第二个我不知道如何处理。我是 Google Cloud 的新手,不知道如何授予对 VPC 服务边界中的 Cloud Storage API 的访问权限。
有人可以帮助我如何处理有关云存储 API 的 VPC 服务边界和访问级别来解决此问题吗?或者错误可能来自不同的原因。
我怀疑(除非您明确启用它),VPC 是一个转移注意力的东西,您已经以某种方式破坏了项目的 IAM 策略。
App Engine 灵活服务帐号是 Compute Engine 默认服务帐号:
${NUMBER}[email protected]
地点:
PROJECT="..."
gcloud projects describe ${PROJECT} \
--format="value(projectNumber)"
因此,确认(根据错误)它有
roles/appengineflex.serviceAgent
:
gcloud projects get-iam-policy ${PROJECT} \
--flatten="bindings[].members" \
--format='table(bindings.role)' \
--filter="bindings.members:${NUMBER}[email protected]"
我重现了您的部署,创建了一个新项目,奇怪的是,这些服务帐户都没有直接授予“存储对象查看器”(
roles/storage.objectViewer
),而计算引擎默认服务帐户则被授予 Editor
(广泛权限)。
我怀疑 Google 修改了它使用的角色(大概具有类似的权限)。尽管 Container|ArtifactRegistry 使用 Cloud Storage,但直接指定存储角色是不合时宜的。
新创建项目的政策包括:
bindings:
- members:
- serviceAccount:service-${NUMBER}@gcp-gae-service.iam.gserviceaccount.com
role: roles/appengine.serviceAgent
- members:
- serviceAccount:service-${NUMBER}@gae-api-prod.google.com.iam.gserviceaccount.com
role: roles/appengineflex.serviceAgent
- members:
- serviceAccount:service-${NUMBER}@gcp-sa-artifactregistry.iam.gserviceaccount.com
role: roles/artifactregistry.serviceAgent
- members:
- serviceAccount:${NUMBER}@cloudbuild.gserviceaccount.com
role: roles/cloudbuild.builds.builder
- members:
- serviceAccount:service-${NUMBER}@gcp-sa-cloudbuild.iam.gserviceaccount.com
role: roles/cloudbuild.serviceAgent
- members:
- serviceAccount:service-${NUMBER}@compute-system.iam.gserviceaccount.com
role: roles/compute.serviceAgent
- members:
- serviceAccount:service-${NUMBER}@containerregistry.iam.gserviceaccount.com
role: roles/containerregistry.ServiceAgent
- members:
- serviceAccount:${NUMBER}[email protected]
- serviceAccount:${NUMBER}@cloudservices.gserviceaccount.com
- serviceAccount:${PROJECT}@appspot.gserviceaccount.com
role: roles/editor
- members:
- serviceAccount:service-${NUMBER}@gcp-sa-firestore.iam.gserviceaccount.com
role: roles/firestore.serviceAgent
- members:
- user:${USER}
role: roles/owner
- members:
- serviceAccount:service-${NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com
role: roles/pubsub.serviceAgent
etag: ${ETAG}
version: 1