一、基本概念
在全托管式的无服务器平台上构建可扩缩性极强的应用, 将应用从零无缝扩容到全球级规模,而不用费心管理底层基础架构。得益于零服务器管理和零配置部署,开发者可以专注于构建出色的应用,省去管理开销。App Engine 支持多种主流开发语言以及各种开发者工具,可帮助开发者提高工作效率和灵活性。
在其他云上与之对标的产品有:
- Azure上的App Service,猛戳这里
- AWS 上的AWS Elastic Beanstalk
- 阿里云上的Web应用托管服务(Web+)
它有两种环境,通常我们使用第一种
二、 快速上手(python3)
(一)、创建应用及app.yaml文件
先上目录,创建得是一个基于python3.8的一个Flask应用,它足够简单,以便我们只关注感兴趣的部分。
main.py 文件:是应用程序的入口,代码如下,用户访问此网站时,会显示方法show_hello返回的内容
import os
from flask import Flask
from dotenv import load_dotenv, find_dotenv
app = Flask(__name__)
@app.route('/')
def show_hello():
myname = os.getenv('myname')
return myname
if __name__ == '__main__':
load_dotenv()
app.run(port=1234)
requirements.txt文件:是依赖包
python-dotenv
flask
.env文件:是配置环境变量的,也就是说当用户访问此应用的时候,在本地运行会直接会显示congcong这个名字。它是敏感文件。
myname=congcong
以上是Flask Web应用的基本的几个文件,下面我们来看一下gcloud需要的文件
app.yaml文件:最重要就是它了,它定义了当前应用在GCP上的运行时是python3.7,服务的名字是default,设置了一个环境变量myname,在我们在GCP上部署好此应用后,那网页上就会显示myname的值。
注意:App Engine上第一个服务的服务名必须是default,第二个至第N个的务的名字随便你自己定义,不然它会报错”ERROR: (gcloud.app.deploy) INVALID_ARGUMENT: The first service (module) you upload to a new application must be the 'default' service (module). “,它会将我们的代码上传到GCP的某个地方。
runtime: python37
service: default
env_variables:
myname: "Hello, this is from environment"
.gcloudignore文件:由于python3中的虚拟环境venv文件里面的内容较多,如果将它部署上去显然有些费时费力,我们需要把一些不需要的文件都排除掉,使用.gcloudignore
.gcloudignore
.git
.gitignore
# Python pycache:
__pycache__/
# Ignored by the build system
/setup.cfg
venv
.env
(二)、部署应用到GCP
下载并安装Google SDK, 右击以管理员身份运行,并配置好Service Account, 如果不会配置,猛戳如何创建并使用service account直接查看里面的第二节,之后你就可以运行如下命令进行部署了,关键的命令只有一行
gcloud app deploy app.yaml
只需要上面的一行命令就可以把本地的代码部署到云端,这么智能? 因为用到了DevOps,实际上就是上面提到的Google的一个pipeline 服务。下面是完整的命令行及提示:
# 运行以下命令,安装包含 Python 3.7 版 App Engine 扩展程序的 gcloud 组件:
gcloud components install app-engine-python
# 定位到你项目的根目录,即包含app.yaml的文件夹下
cd C:\CongStudy\python-flask-nginx-demo
# 运行此命令部署你的应用程序,后面的app.yaml可以省略
c:\CongStudy\python-flask-nginx-demo>gcloud app deploy app.yaml
You are creating an app for project [qwiklabs-gcp-01-dbddd1fe79f0].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
<https://cloud.google.com/appengine/docs/locations>.
Please choose the region where you want your App Engine application
located:
[1] asia-east2 (supports standard and flexible)
[2] asia-northeast1 (supports standard and flexible)
[3] asia-northeast2 (supports standard and flexible)
[4] asia-northeast3 (supports standard and flexible)
[5] asia-south1 (supports standard and flexible)
...
[18] cancel
Please enter your numeric choice: 1
Creating App Engine application in project [qwiklabs-gcp-01-dbddd1fe79f0] and region [asia-east2]..
..done.
Services to deploy:
descriptor: [c:\CongStudy\python-flask-nginx-demo\app.yaml]
source: [c:\CongStudy\python-flask-nginx-demo]
target project: [qwiklabs-gcp-01-dbddd1fe79f0]
target service: [default]
target version: [20200314t165844]
target url: [https://qwiklabs-gcp-01-dbddd1fe79f0.appspot.com]
Do you want to continue (Y/n)? y
Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 2 files to Google Cloud Storage ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://qwiklabs-gcp-01-dbddd1fe79f0.appspot.com]
You can stream logs from the command line by running:
$ gcloud app logs tail -s default
To view your application in the web browser run:
$ gcloud app browse
# 运行此命令,它会在浏览器中打开此网站
c:\CongStudy\python-flask-nginx-demo>gcloud app browse
Opening [https://qwiklabs-gcp-01-dbddd1fe79f0.appspot.com] in a new tab in your default browser.
(三)、效果
这是网页
这是在GCP控制台看到的截图
到这里,你已经基本掌握了app engine的用法了。但仍然还有许多的问题需要解决:
- 它的实例默认是随着访问量自动扩缩的,是如何自动扩缩的呢,我可以控制吗?
- 它自动扩缩时,肯定是以某一代码源或者artifact为基础,那我上传的代码放在哪里呢?
- 如何绑定自定义域名及安全证书,当实例数自动扩展时,它是如何将证书自动安装到新的实例上?
- 如果我有多个服务,却只有一个域名,如何通过一个域名访问到多个服务,即反向代理是咋整的?
- 多个服务中,服务与服务之间如何调用,它们在同一个局域网内吗? 互相访问时可以走内网吗,走外网就慢了。
- 上图中有一个version 1,可以看出每个服务都有版本控制的,不同版本之间我能对流量进行控制吗?
- 灰度部署/蓝绿部署/AB测试这些都支持么?
- 对这些服务如何追踪和监控,有日志么,在哪里看到我的服务的使用情况。
三、 设计应用
为了回答上面所有的问题,只有一个服务显然不够。
以下示例展示了当您在本地开发应用时,一个含有三项服务的应用可能具有的结构。可选的 dispatch.yaml 已添加到该应用的根目录中。在根目录下还有三个目录,分别对应于应用的每项服务。service1 的子目录包含该服务的源文件和配置文件。同样,service2 和 service3 均位于独立的目录中,这些目录分别包含对应服务的文件,而 service3 包含 YAML 配置文件的两个版本:
上面的service<num>.yaml,如service1.yaml,和我们之间定义的app.yaml是同类型文件,它定义了运行时的环境等,专门用于部署的。但上图中多出来了一个dispatch.yaml,从字面上就可以看出它是用于路由分发的,即所谓的反向代理。这里我们先列出所有的配置文件出来,共有4个:
(一)、app.yaml
这个我们已经见过了,不仅定义了运行时的环境,而且可以处理扩缩、缓存等,这里我们列出一个稍复杂的例子。
runtime: python37 # 定义运行时
service: service_name # 定义你的服务名,注意第一个服务名只能叫default
instance_class: F2 # 定义实例了类型,类型有F1、F2、F4、F4_1G
env_variables: #定义环境变量
BUCKET_NAME: "example-gcs-bucket"
handlers: # 配置单个服务内的路由
# Matches requests to /images/... to files in static/images/...
- url: /images
static_dir: static/images
http_headers: # 还可以设置http头哦
X-Foo-Header: foo
X-Bar-Header: bar value
Access-Control-Allow-Origin: http://mygame.appspot.com # 来个CORS 跨域支持
- url: /.* # 重定向
secure: always
redirect_http_response_code: 301
script: auto
error_handlers: # 对错误请求的处理
- file: default_error.html
- error_code: over_quota
file: over_quota.html
# 自动扩缩的配置在这里
automatic_scaling:
target_cpu_utilization: 0.65 # CPU使用率达到 65% 后会启动新实例
min_instances: 5 # 最小实例数是5
max_instances: 100 # 最大实例数是100
min_pending_latency: 30ms # 最小冷却时间,即允许请求在待处理队列中等待的最短时间。当处理请求时达到某一阈值后,不会立即扩缩,会等待30ms,例如:当服务过载时,一个请求在待处理队列中等待,appengine不会立即新增实例,至少等待30ms才会扩展实例数, 30ms之前肯定不会扩展实例数
max_pending_latency: 100ms # 最大冷却时间,App Engine 允许某请求在待处理队列中等待的最长时间。App Engine 可以在“min-pending-latency”与“max-pending-latency”中指定的时间之间随时创建实例。也就是说,App Engine 不会在“min-pending-latency”中指定的时间之前创建实例来处理待处理请求,但 App Engine 会在达到“max-pending-latency”之后创建实例。
max_concurrent_requests: 50 # 自动扩缩实例可接受的并发请求数
max_idle_instances # App Engine 应为此版本保留的最大空闲实例数
# 手动扩缩
manual_scaling:
instances: 5 # 在开始时分配给服务的实例数
(二)、dispatch.yaml
定义路由规则的配置文件,顾名思义,它就是通过url中的path来查找相应的服务的。例如下面有三个服务分别是service1/service2/service3,你只有一个域名是www.example.com,当你访问www.example.com/BBB的时候,实例调用得服务是service2
dispatch:
- url: 'www.example.com/default/*'
service: default
- url: 'www.example.com/AAA*'
service: service1
- url: 'www.example.com/BBB*'
service: service2
- url: 'www.example.com/CCC*'
service: service3
如何部署此文件,只需要一行命令,如下:
gcloud app deploy dispatch.yaml
来一个实际的例子,如下图:
看到这里相信你应该明白的三点:
- 为什么第一个服务名必须是default, 因为当你不定义dispatch.yaml时,即没有路由规则时,它会调用服务default
- 拿我第二节创建的demo为例,可以看出我没有创建dispatch.yaml文件,所以上图红色块中是为空的。
- 只有先部署了你的所有服务之后,才能部署dispatch.yaml文件。即运行了gcloud app deploy app.yaml 后,才能运行gcloud app deploy dispatch.yaml
(三)、cron.yaml
这个配置文件是可选的,它是搞定期计划任务的。涨下面这样,就不多解释了
cron:
- description: "daily summary job"
url: /tasks/summary
schedule: every 24 hours
- description: "monday morning mailout"
url: /mail/weekly
schedule: every monday 09:00
timezone: Australia/NSW
- description: "new daily summary job"
url: /tasks/summary
schedule: every 24 hours
target: beta
如何部署此文件,只需要一行命令,如下:
gcloud app deploy cron.yaml
(四)、index.yaml
这个配置文件更可选了,不想研究了,是配置 Datastore 索引,有了它,执行查询时,Datastore 可快速返回结果。下面是官网解释。
您可以将标准环境中运行的应用的数据存储在 Cloud Datastore 中。Cloud Datastore 使用索引来处理应用执行的每个查询。实体发生变化时这些索引也会得到更新,因此在应用执行查询时,Datastore 可快速返回结果。
四、服务之间通信
重要!单独列为一节,要与您的 App Engine 服务进行通信,最简单的方法是发送定向 HTTP 请求,在网址中包含资源的名称或 ID。例如,除相应的 GCP 项目 ID 之外,您还可以包含要定位的服务或版本的 ID:
http://[VERSION_ID].[SERVICE_ID].[MY_PROJECT_ID].appspot.com
https://[VERSION_ID]-dot-[SERVICE_ID]-dot-[MY_PROJECT_ID].appspot.com
App Engine 服务还可以使用 Cloud Pub/Sub 通信,以便在进程(包括 App Engine)之间提供可靠的异步多对多消息传递。
五、AppEngine存储数据和文件
App Engine只允许你存入临时文件,只能存入 /tmp
目录。 我在实际项目中用到了,所以列出来了,虽然不是重点。
AppEngine里的实例原则上是不允许你存任何东西的,因为自动扩缩的时候,实例随时会销毁,实例销毁后,你存的东西全部会删除。但有时候程序需要存一些临时文件,举个例子:用户点击下载时生成一个Excel文件、压缩一下再传到Google Storage里供用户下载,那个Excel文件就需要临时存一下。
六、使用自定义域名和安全证书
直接在控制台里面配置就可以了,注意这里是cname,将AppEngine自动生成的域名和你定义的域名在域名解析的地方绑定起来就好了。AppEngine自动生成的域名与它背后的服务器之间是天生自带负载匀衡(7层/4层)的,不需要我们作额外的控制。
七、流量管理
一些相关的概念
- 蓝绿部署(Blue/Green Deployment): 它是最常见的一种0 downtime部署的方式,就是准备两个版本,旧版本A在生环境中正常运行,要发部署新版本B后,直接将流量切换到新版本B,如果测试发现新版本B有问题,可以将流量快速切回到旧版本A。
- 红黑部署(Red-Black Deployment):与蓝绿部署很相似,蓝绿表示生产环境有两个版本都可用,绿表示生产环境正在用,蓝表也可用,但是个备胎;红黑部署中,红是正在用,黑是不可用的。但在云环境中,这两个概念似乎弱化了,感觉就是同一个意思。
- 灰度发布/金丝雀发布:就是流量拆分,例如90%的用户维持使用老版本,10%的用户尝鲜新版本。
- 滚动发布(rolling update):一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用。周而复始,直到集群中所有的实例都更新成新版本,例如每次只取出集群的20%进行升级,Kubernetes中的滚动升级就是属于这种。
- A/B 测试(A/B Testing): A版本是线上稳定版本,B版本是新版本,如果一下子切到B环境,可能用户会难以适应,所以先部署一个B环境,分一部分流量出来,收集用户反馈后逐步改进B版本,直到用户可以完全接受用B版本替换A版本的程度。
总结:它们之间都是有区别的。 另外,灰度发布经常与A/B 测试一起使用,用于测试选择多种方案。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。
当一个服务有多个版本时,有两种办法来控制多个版本之间的流量
(一)、部署一个新的版本后,将流量100%从旧版本切换到新版本(Swap方式)
适合小项目,扎心了,没权限,但它就在这里,如下图。
但有个小问题,它是属于蓝绿部署,还是红黑部署呢? 因为对于流量为0版本我也不知道它有没有死,并且它不止两个版本,如果有7个版本,你准备怎么称呼?
(二)、部署一个新的版本后,对流量进行拆分
例如,你担心这个新版本可能会有问题,只将一部分流量,比如5%分给新的版本,剩余95%继续给旧版本。点击下图的按钮就可以拆分流量,前提是要有至少2个版本。
那它可以有以下几种形式进行拆:
-
IP 地址拆分: 当该应用收到请求时,它会将 IP 地址哈希处理为介于 0-999 之间的值,并使用该数字来路由请求。IP 地址拆分具有很大的局限性,比如IP 地址可能会不断变化,用户一会用新版本一会用旧版本,体验不好。
- Cookie 拆分:应用会在HTTP头部查找名为
GOOGAPPUID
的 Cookie,该 Cookie 中包含一个 0 至 999 之间的值,如果存在此 Cookie,则使用该值路由请求,如果没有此 Cookie,则会随机路由请求。
总结:
- 流量拆分最容易遇到缓存问题,比如A和B两个版本,新版本B对CSS文件作改动了,而用户本地缓存了此文件,A 和 B 两个版本之间拆分流量后,很可能导致旧版本使用新的CSS文件,最好是在HTTP头部加上Cache-Control和Expires来解决。
- 服务与服务之间的调用最好改用 Cookie 拆分。
- 这里能按人拆分?例如:只有张三登录才能使用最新版本。 不支持,只有以上两种方式!!!
八、日志与监控
GCP有专门的服务来解决这类问题。
- 查日志请访问Logging
- 查流量、与监控请访问Trace, "projects/[PROJECT_ID]/traces/[TRACE_ID]"
九、总结
还记得上面第2节中的8个问题么? 还有一个问题没有回答。
1. ”它自动扩缩时,肯定是以某一代码源或者artifact为基础,那我上传的代码放在哪里呢“ ?
答:它会将你上传的代码或artifact放在Google Storage中,当你发布你的应用时,它会自动在Google Storage创建一个Buket来存放你的代码。
2. 我们创建的Python Web应用,它得Web服务器是用得什么,为什么只需要上传代码后Web应用自动能运行?
答:Python Web应用最常使用得Web服务器是uswgi,当你部署代码时,实际上你的代码会被打包成一个自定义的Docker Image里,当你选择运行时时,这个对应的Base Image也就选定好了,Base Image是Google预先定义的,里面早已有了Python需要的Web服务器及相应的组件,默认使用gunicorn
用作 Web 服务器,参考这里
3. 上面的8个问题归纳一下
答:用一话来说,就是你的服务治理是如何实现的,我们可以将这些问题总结为4点:连接、保护、控制、观测。这些问题由GCP平台都帮你实现了,我们只需要使用就可以了,如果你想继续深入的研究,可以学习微服务,本主将会后绪一一更新。解决问题的能力来源于你深入的研究与实践,加油!
4. 注意事项
答:AppEngine应用应该是“无状态”的,且实例上不存储任何内容。
5. 既然AppEngine包含服务治理的功能,岂不是把Api Gateway的活给干了?那还需要配置Api Gateway吗?
答:可以和API Gateway结合使用,也可以单独使用,如果结合使用,请参考这里
参考文献
https://cloud.google.com/appengine/docs/standard/python3