1. 概览
本实验演示了一些特性和功能,这些特性和功能旨在简化在容器化环境中开发 Python 应用的软件工程师的开发工作流。典型的容器开发要求用户了解容器和容器构建流程的详细信息。此外,开发者通常需要中断他们的流程,离开 IDE,以在远程环境中测试和调试其应用。借助本教程中提到的工具和技术,开发者无需离开 IDE 即可高效使用容器化应用。
学习内容
在本实验中,您将学习在 GCP 中使用容器进行开发的方法,包括:
- 创建新的 Python 起始应用
- 浏览开发过程
- 开发简单的 CRUD 静态服务
2. 设置和要求
自定进度的环境设置
- 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个。
- 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串,您可以随时对其进行更新。
- 项目 ID 在所有 Google Cloud 项目中必须是唯一的,并且不可变(一经设置便无法更改)。Cloud Console 会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(它通常标识为
PROJECT_ID
),因此如果您不喜欢某个 ID,请再生成一个随机 ID,还可以尝试自己创建一个,并确认是否可用。然后,项目创建后,ID 会处于“冻结”状态。 - 第三个值是一些 API 使用的项目编号。如需详细了解所有这三个值,请参阅文档。
- 接下来,您需要在 Cloud Console 中启用结算功能,才能使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有费用的话)。要关闭资源以避免产生超出本教程范围的费用,请按照此 Codelab 末尾提供的任何“清理”说明操作。Google Cloud 的新用户符合参与 $300 USD 免费试用计划的条件。
启动 Cloudshell 编辑器
本实验旨在与 Google Cloud Shell Editor 搭配使用,并经过测试。要访问该编辑器,请按以下步骤操作:
- 通过 https://console.cloud.google.com 访问您的 Google 项目。
- 点击右上角的 Cloud Shell 编辑器图标
- 窗口底部会打开一个新窗格
- 点击“打开编辑器”按钮
- 编辑器将打开,右侧为探索器,中心区域为编辑器
- 屏幕底部还应提供一个终端窗格
- 如果终端未打开,请使用 `ctrl+` 的组合键打开新的终端窗口
环境设置
在 Cloud Shell 中,设置项目 ID 和项目编号。将它们保存为 PROJECT_ID
和 PROJECT_ID
变量。
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID \
--format='value(projectNumber)')
获取源代码
- 本实验的源代码位于 GitHub 上 GoogleCloudPlatform 的 container-developer-workshop 中。使用以下命令克隆该文件,然后切换到该目录。
git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git &&
cd container-developer-workshop/labs/python
mkdir music-service && cd music-service
cloudshell workspace .
如果终端未打开,请使用 `ctrl+` 的组合键打开新的终端窗口
预配本实验中使用的基础架构
在本实验中,您会将代码部署到 GKE,并访问存储在 Spanner 数据库中的数据。下面的设置脚本会为您准备此基础架构。预配过程将需要 10 分钟以上。在设置处理期间,您可以继续执行接下来的几个步骤。
../setup.sh
3. 创建新的 Python 起始应用
- 创建名为
requirements.txt
的文件,并将以下内容复制到其中
Flask
gunicorn
google-cloud-spanner
ptvsd==4.3.2
- 创建名为
app.py
的文件,并将以下代码粘贴到其中
import os
from flask import Flask, request, jsonify
from google.cloud import spanner
app = Flask(__name__)
@app.route("/")
def hello_world():
message="Hello, World!"
return message
if __name__ == '__main__':
server_port = os.environ.get('PORT', '8080')
app.run(debug=False, port=server_port, host='0.0.0.0')
- 创建一个名为 Dockerfile 的文件,并将以下内容粘贴到其中
FROM python:3.8
ARG FLASK_DEBUG=0
ENV FLASK_DEBUG=$FLASK_DEBUG
ENV FLASK_APP=app.py
WORKDIR /app
COPY requirements.txt .
RUN pip install --trusted-host pypi.python.org -r requirements.txt
COPY . .
ENTRYPOINT ["python3", "-m", "flask", "run", "--port=8080", "--host=0.0.0.0"]
注意:借助 FLASK_DEBUG=1,您可以将代码更改自动重新加载到 Python Flask 应用中。此 Dockerfile 允许您将此值作为构建参数传递。
生成清单
在终端中执行以下命令,以生成默认的 skaffold.yaml 和 Deployment.yaml。
- 使用以下命令初始化 Skaffold
skaffold init --generate-manifests
出现提示时,使用箭头移动光标,使用空格键选择所需选项。
选择:
8080
(针对端口)y
(用于保存配置)
更新 Skaffold 配置
- 更改默认应用名称
- 打开
skaffold.yaml
- 选择当前设为“
dockerfile-image
”的映像名称 - 右键点击并选择“更改所有出现次数”
- 输入新名称
python-app
- 进一步修改构建部分
- 添加
docker.buildArgs
以传递FLASK_DEBUG=1
- 同步设置,以将对
*.py
文件所做的任何更改从 IDE 加载到正在运行的容器
修改后,skaffold.yaml
文件中的 build 部分将如下所示:
build:
artifacts:
- image: python-app
docker:
buildArgs:
FLASK_DEBUG: 1
dockerfile: Dockerfile
sync:
infer:
- '**/*.py'
修改 Kubernetes 配置文件
- 更改默认名称
- 打开
deployment.yaml
文件 - 选择当前设为“
dockerfile-image
”的映像名称 - 右键点击并选择“更改所有出现次数”
- 输入新名称
python-app
4. 开发过程介绍
添加业务逻辑后,您现在可以部署和测试应用了。以下部分将重点介绍 Cloud Code 插件的用法。除此之外,此插件还可与 Skaffold 集成,以简化您的开发流程。当您按以下步骤部署到 GKE 时,Cloud Code 和 Skaffold 会自动构建容器映像,将其推送到 Container Registry,然后将应用部署到 GKE。这是在后台将细节从开发者流程中提取出来的。
将容器部署到 Kubernetes
- 在 Cloud Shell Editor 底部的窗格中,选择 Cloud Code 
- 在顶部显示的面板中,选择 Run on Kubernetes。如果出现提示,请选择“Yes”以使用当前的 Kubernetes 上下文。
此命令会启动源代码的构建,然后运行测试。构建和测试将需要几分钟时间才能运行完毕。这些测试包括单元测试和验证步骤,用于检查为部署环境设置的规则。此验证步骤已配置,确保即使您仍在开发环境中工作,也能收到部署问题的警告。
- 首次运行此命令时,屏幕顶部会显示一条提示,询问您是否需要当前的 Kubernetes 上下文,请选择“是”接受并使用当前上下文。
- 接下来,系统会显示一条提示,询问要使用哪个容器注册表。按 Enter 键可接受提供的默认值
- 选择下部窗格中的“Output”(输出)标签页可查看进度和通知
- 选择“Kubernetes:运行/调试 - 详细”查看右侧渠道下拉菜单中的 其他详细信息和实时从容器流式传输的日志
构建和测试完成后,“Output”(输出)标签页会显示 Attached debugger to container "python-app-8476f4bbc-h6dsl" successfully.
,并列出网址 http://localhost:8080。
- 在 Cloud Code 终端中,将鼠标悬停在输出结果中的第一个网址 (http://localhost:8080) 上,然后在显示的工具提示中选择“打开网页预览”。
- 系统会打开一个新的浏览器标签页,并显示以下消息:
Hello, World!
热重载
- 打开
app.py
文件 - 将问候语消息更改为
Hello from Python
请注意,在 Output
窗口的 Kubernetes: Run/Debug
视图中,Watcher 将更新后的文件与 Kubernetes 中的容器同步
Update initiated Build started for artifact python-app Build completed for artifact python-app Deploy started Deploy completed Status check started Resource pod/python-app-6f646ffcbb-tn7qd status updated to In Progress Resource deployment/python-app status updated to In Progress Resource deployment/python-app status completed successfully Status check succeeded ...
- 如果切换到
Kubernetes: Run/Debug - Detailed
视图,您会注意到它可以识别文件更改,然后构建并重新部署应用
files modified: [app.py]
Syncing 1 files for gcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Copying files:map[app.py:[/app/app.py]]togcr.io/veer-pylab-01/python-app:3c04f58-dirty@sha256:a42ca7250851c2f2570ff05209f108c5491d13d2b453bb9608c7b4af511109bd
Watching for changes...
[python-app] * Detected change in '/app/app.py', reloading
[python-app] * Restarting with stat
[python-app] * Debugger is active!
[python-app] * Debugger PIN: 744-729-662
- 刷新浏览器以查看更新后的结果。
调试
- 转到“调试”视图并停止当前线程
。
- 点击底部菜单中的
Cloud Code
,然后选择Debug on Kubernetes
以在debug
模式下运行应用。
- 在
Output
窗口的Kubernetes Run/Debug - Detailed
视图中,请注意 Skaffold 将在调试模式下部署此应用。
- 第一次执行此操作时,系统会显示一条提示,询问数据源在容器中的什么位置。此值与 Dockerfile 中的目录相关。
按 Enter 键接受默认值
构建和部署应用需要几分钟的时间。
- 该过程完成时。您会看到连接了调试程序。
Port forwarding pod/python-app-8bd64cf8b-cskfl in namespace default, remote port 5678 -> http://127.0.0.1:5678
- 底部状态栏的颜色从蓝色变为橙色,表示它处于调试模式。
- 在
Kubernetes Run/Debug
视图中,请注意启动了一个 Debuggable 容器
**************URLs***************** Forwarded URL from service python-app: http://localhost:8080 Debuggable container started pod/python-app-8bd64cf8b-cskfl:python-app (default) Update succeeded ***********************************
利用断点
- 打开
app.py
文件 - 找到显示
return message
的语句 - 点击行号左侧的空白处,为该行添加断点。系统会显示一个红色指示器,指明断点已设置
- 重新加载浏览器,并注意调试程序会在断点停止进程,并允许您调查在 GKE 中远程运行的应用变量和状态
- 点击向下进入“变量”部分
- 点击“Locals”,即可找到
"message"
变量。 - 双击变量名称“message”在弹出式窗口中,将值更改为其他值,例如
"Greetings from Python"
- 点击调试控制台中的“继续”按钮
- 在浏览器中查看响应,浏览器现在会显示您刚刚输入的更新值。
- 停止“调试”进入模式,然后再次点击停止按钮
以移除该断点。
5. 开发简单的 CRUD 静态服务
至此,您的应用已完全针对容器化开发进行了配置,并且您已完成 Cloud Code 的基本开发工作流。在以下部分中,您将通过添加连接到 Google Cloud 中的代管式数据库的 REST 服务端点,练习所学知识。
对其余服务进行编码
以下代码创建了一个简单的静态服务,该服务使用 Spanner 作为为应用提供支持的数据库。如需创建应用,请将以下代码复制到您的应用中。
- 通过将
app.py
替换为以下内容来创建主应用
import os
from flask import Flask, request, jsonify
from google.cloud import spanner
app = Flask(__name__)
instance_id = "music-catalog"
database_id = "musicians"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
@app.route('/singer', methods=['POST'])
def create():
try:
request_json = request.get_json()
singer_id = request_json['singer_id']
first_name = request_json['first_name']
last_name = request_json['last_name']
def insert_singers(transaction):
row_ct = transaction.execute_update(
f"INSERT Singers (SingerId, FirstName, LastName) VALUES" \
f"({singer_id}, '{first_name}', '{last_name}')"
)
print("{} record(s) inserted.".format(row_ct))
database.run_in_transaction(insert_singers)
return {"Success": True}, 200
except Exception as e:
return e
@app.route('/singer', methods=['GET'])
def get_singer():
try:
singer_id = request.args.get('singer_id')
def get_singer():
first_name = ''
last_name = ''
with database.snapshot() as snapshot:
results = snapshot.execute_sql(
f"SELECT SingerId, FirstName, LastName FROM Singers " \
f"where SingerId = {singer_id}",
)
for row in results:
first_name = row[1]
last_name = row[2]
return (first_name,last_name )
first_name, last_name = get_singer()
return {"first_name": first_name, "last_name": last_name }, 200
except Exception as e:
return e
@app.route('/singer', methods=['PUT'])
def update_singer_first_name():
try:
singer_id = request.args.get('singer_id')
request_json = request.get_json()
first_name = request_json['first_name']
def update_singer(transaction):
row_ct = transaction.execute_update(
f"UPDATE Singers SET FirstName = '{first_name}' WHERE SingerId = {singer_id}"
)
print("{} record(s) updated.".format(row_ct))
database.run_in_transaction(update_singer)
return {"Success": True}, 200
except Exception as e:
return e
@app.route('/singer', methods=['DELETE'])
def delete_singer():
try:
singer_id = request.args.get('singer')
def delete_singer(transaction):
row_ct = transaction.execute_update(
f"DELETE FROM Singers WHERE SingerId = {singer_id}"
)
print("{} record(s) deleted.".format(row_ct))
database.run_in_transaction(delete_singer)
return {"Success": True}, 200
except Exception as e:
return e
port = int(os.environ.get('PORT', 8080))
if __name__ == '__main__':
app.run(threaded=True, host='0.0.0.0', port=port)
添加数据库配置
如需安全地连接到 Spanner,请将应用设置为使用 Workload Identities。这样,您的应用就可以充当自己的服务账号,并在访问数据库时拥有单独的权限。
- 更新
deployment.yaml
。在文件末尾添加以下代码(确保保持下例中的制表符缩进)
serviceAccountName: python-ksa
nodeSelector:
iam.gke.io/gke-metadata-server-enabled: "true"
部署并验证应用
- 在 Cloud Shell 编辑器底部的窗格中,选择
Cloud Code
,然后选择屏幕顶部的Debug on Kubernetes
。 - 构建和测试完成后,“Output”(输出)标签页会显示
Resource deployment/python-app status completed successfully
,并列出了一个网址:“Forwarded 网址 from service python-app: http://localhost:8080” - 添加几个条目。
从 cloudshell 终端运行以下命令
curl -X POST http://localhost:8080/singer -H 'Content-Type: application/json' -d '{"first_name":"Cat","last_name":"Meow", "singer_id": 6}'
- 在终端中运行以下命令来测试 GET
curl -X GET http://localhost:8080/singer?singer_id=6
- 测试删除:现在,尝试运行以下命令来删除条目。根据需要更改 item-id 的值。
curl -X DELETE http://localhost:8080/singer?singer_id=6
This throws an error message
500 Internal Server Error
找出并解决问题
- 调试模式并找出问题。请参考以下提示:
- 我们知道 DELETE 出错了,因为它没有返回所需的结果。因此,您需要在
delete_singer
方法的app.py
中设置断点。 - 逐步执行执行,并观察每一步的变量,观察左侧窗口中局部变量的值。
- 如需观察特定值(如
singer_id
和request.args
),请将这些变量添加到 Watch 窗口。
- 请注意,分配给
singer_id
的值为None
。更改代码以解决问题。
修复后的代码段将如下所示。
@app.route('/delete-singer', methods=['DELETE', 'GET']) def delete_singer(): try: singer_id = request.args.get('singer_id')
- 重启应用后,通过尝试删除应用再次测试。
- 点击调试工具栏
中的红色方块,停止调试会话
6. 清理
恭喜!在本实验中,您从头开始创建了一个新的 Python 应用,并将其配置为与容器高效运行。然后,您按照传统应用堆栈中的相同开发者流程,将应用部署并调试到远程 GKE 集群。
在完成实验后进行清理:
- 删除实验中使用的文件
cd ~ && rm -rf container-developer-workshop
- 删除项目以移除所有相关基础架构和资源