查看原文
其他

9.9 元的服务器太贵,继续降本十倍

zirali 咋用云 2024-01-03


国内云计算厂商每年的大促活动,跟淘宝双十一有的一拼。

我前两天去各个云登录了一圈对比了下云产品规格,然后我的手机就被各家云厂商的销售打爆了,不约而同的话术就是:我们有活动,买服务器便宜,还有更低的折扣呢!

我对这些电话的内容感到非常无语。做为用户,我希望的是能够在安全可靠的基础上,花更少的人力成本和普惠价格完成自己的业务支撑。

价格当然是因素之一,但不是全部啊。电话上来不问具体的需求,或者问了具体的需求还是直接说服务器便宜,要不要买几台啊?

这种电销模式带来的无非两种客户:

  1. 单用服务器、硬盘等基础产品搭建服务的用户,唯一的诉求就是能便宜。

  2. 学生或者个人尝鲜开发者,少喝两杯咖啡省出来一个小服务器然后自己玩玩。

上云的衡量

如果你只是一个人想自己玩一下,那参与这种优惠没啥问题,反正没啥生产数据,第二年续费贵了之后再换一家就是,哪家的羊毛不是薅啊?

但如果你是要给一个企业或者商家做一个上云方案,那价格这方面就可能需要往后放一放了,我们要先做架构选型,然后再谈价格的问题。

不同的场景需求,都应该有其适合的架构。衡量架构是否合适的指标有几点:

  1. 可靠性: 在客户业务需求和访问情况确定时,对请求成功率是否有要求,是否可以接受短暂服务不可用。我觉得没客户会主动说接受不可用,架构师需要评估服务停摆带来的损失,来判断是否值得用更可靠的设计来尽量减少服务不可用带来的损失。

  2. 时效性: 客户是否关心数据的时效性,读写是否必须同步。比如商品秒杀就非常在意数据时效性,而社区文章阅读点赞则可以不做数据实时同步。时效性的要求对架构的产品选型至关重要,它也有效决定了最终架构方案的价格。

  3. 整体价格: 在客户能预测的服务时间区间中,服务运行成本加区间内需要的技术和人工成本能接受的预算有多少。

以上这几点,首先应该考虑的是可靠性和时效性,给出相应的一种或几种方案,最后再去比较价格;确定最优的方案后,去各大云厂商比较价格。

我一直认为世界上不存在什么云计算标准架构,有的也只有适用场景需求的推荐架构。

用户的需求

我自己闲暇时间也会做一些线上的工具类产品,用云计算来为这些产品提供服务支撑。也会有空接一些帮别人参谋的小活,给大大小小的商家或者小企业做上云架构。今天我想分享的案例就来自其中一个商家的实践。

这个商家做预制菜礼品,需要在不同规格的礼盒上贴二维码,用户扫码能看到该规格礼盒的一些信息。商家希望购买者微信扫码直接能打开微信小程序。礼盒规格不到 10 种,信息更新频率最多 2 次/年。

之前该商家用的一些表单类的 SAAS 小程序,生成页面展示。当商家想要有自己主体的小程序,之前的 SAAS 方案收费就有些贵。所以想自己建小程序,持续运行的成本尽量低。

我大概评估了一下需求,既然更新频率不高,那直接把规格信息写死放在微信小程序里,每年几次更新重新发版就是了,一点后端服务都没有,持续运行成本为 0。

在说服商家把二维码改成小程序码的过程中,明显感觉商家还是有自己小心思的。后面他希望能在其他 APP 扫码也能打开这个 APP 的小程序。说麦当劳肯德基就是这么做的,他也要这个感觉。

行吧,那这样必须要上一个后端服务了,这个服务主要有两个作用:

  1. 提供一个网址做为各宿主 APP 扫码的基础。做过小程序的同学应该了解扫普通二维码,其实就是在识别网址,根据不同的网址路由打开不同的小程序。

  2. 为未来的多端小程序提供商品信息,并能够在后面同时更新这些信息。

架构方案选型

我们按照刚才的指标过一遍:

  1. 可靠性: 商家是个小商家,购买者扫码只是看信息,不行也可以再扫一次,因此可以接受非 100%请求成功率,但也不能太离谱;也要提防这个商家万一做大了,访问量激增带来的负载问题。

  2. 时效性: 一年两次更新,每次更新都在半夜三更,也不用非要在意什么时效性。

  3. 整体价格: 希望持续运行的成本能越低越好。

由于可靠性和时效性这令人感动的要求,在市面上搞个 JSONBOX图床网盘我都觉得能满足要求。可能只剩下价格这一个指标了。

虽然要求低,但最起码人家还是一个商家,最基本的安全稳定可靠,服务商不跑路才是第一位的,因此我还是直接在云计算厂商里挑选产品组合。

部分开发者直接的想法是,租个长期非常便宜的小规格服务器,部署一个简单的服务,并搭配一个域名和免费的 SSL 证书,这事就解决了。

除了直接操刀基础产品,对云产品的种类特点有些储备的情况下,也会提出下面几种:

  1. 使用无服务器云函数方案,调用一次付一次钱,信息更新直接更新云函数的代码配置。

  2. 使用对象存储,根据流量和调用次数,占用空间按量计费,通过覆盖文件来做信息更新。

  3. 如果在对象存储的基础上进一步搭配 CDN服务,在流量价格方面又能节省一部分。

以上几种也都能搭配自定义域名配免费的 SSL 证书。

在价格方面进一步权衡后,我最终给商家推荐了对象存储+CDN 方案,架构设计如下:

商品信息展示服务架构图

在这个架构中,使用 CDN 服务绑定自定义域名,向对象存储桶回源。小程序根据扫码的链接直接解析出对应的商品信息地址,向 CDN 服务发起请求,得到信息后展示到页面中。

小程序效果图

由于每年 2 次的更新频率,我觉得教商家用「对象存储客户端」自己维护几个信息文件问题不大,因此压根就没有做管理员更新这部分的软件开发。但如果商家的管理是多个人并且成体系的,我倒是可以用云的权限管控(给不同的员工开不同的云子账号)写一个基于对象存储的信息管理控制台,但在这个例子中这么做太浪费了。

下面主要介绍一下我的实施部分,分云服务产品的开通、前端小程序的开发调试两部分。

云服务产品实践

在上云的实践描述中,我都以 terraform 或其他 IaC 形式来展现,以准确清晰表述我所有的配置动作。如果你不了解 IaC 相关知识,可以先简单学习一下再上手。

为了简化大家复制动作,本文的 terraform 代码均写在一个 tf 文件下,你可以自己按规范建议分拆成多个文件。

腾讯云对象存储+CDN 的 tf 文件内容如下,locals 本地值里需要自己调整一下 domain 为自己的域名。

terraform {
required_providers {
tencentcloud = {
source = "tencentcloudstack/tencentcloud"
version = "1.81.60"
}
null = {
source = "hashicorp/null"
version = "3.2.2"
}
}
}

data "tencentcloud_user_info" "info" {}

locals {
app_id = data.tencentcloud_user_info.info.app_id
owner_uin = data.tencentcloud_user_info.info.owner_uin
region = "ap-shanghai" // 希望创建资源的地域
domain = "www.example.com" // 绑定的自定义域名
index_name = "index.html" // 访问中间页路径
index_file = "${path.module}/index.html" // 访问中间页文件
example_id = "0" // 示例参数
example_name = "asset/0.json" // 示例配置路径
example_file = "${path.module}/set.json" // 示例配置文件
cert = file("${path.module}/ssl.crt") // 自定义域名证书crt文件
private_key = file("${path.module}/ssl.key") // 自定义域名证书key文件
}

provider "tencentcloud" {
region = local.region
}

data "tencentcloud_cdn_domain_verifier" "shop_cdnvr" {
domain = local.domain
auto_verify = true
freeze_record = true
}

resource "null_resource" "check_verification" {
count = data.tencentcloud_cdn_domain_verifier.shop_cdnvr.verify_result ? 0 : 1
provisioner "local-exec" {
command = "echo '需要进行${data.tencentcloud_cdn_domain_verifier.shop_cdnvr.record_type}解析验证|${data.tencentcloud_cdn_domain_verifier.shop_cdnvr.sub_domain} | ${data.tencentcloud_cdn_domain_verifier.shop_cdnvr.record}' && exit 1"
}
}

resource "tencentcloud_cos_bucket" "shop_bucket" {
bucket = "shop-${local.app_id}"
acl = "private"
multi_az = true
force_clean = true
website {
index_document = "index.html"
error_document = "index.html"
}
}

resource "tencentcloud_cos_bucket_policy" "cos_policy" {
bucket = tencentcloud_cos_bucket.shop_bucket.id

policy = <<EOF
{
"Statement": [
{
"Principal": {
"qcs": [
"qcs::cam::uin/${local.owner_uin}:service/cdn"
]
},
"Effect": "Allow",
"Action": [
"name/cos:GetObject",
"name/cos:HeadObject",
"name/cos:OptionsObject"
],
"Resource": [
"qcs::cos:ap-shanghai:uid/${local.app_id}:${tencentcloud_cos_bucket.shop_bucket.id}/*"
]
}
],
"version": "2.0"
}
EOF
}

resource "tencentcloud_cos_bucket_object" "upload_index" {
bucket = tencentcloud_cos_bucket.shop_bucket.id
acl = "private"
key = local.index_name
source = local.index_file
}

resource "tencentcloud_cos_bucket_object" "upload_example" {
bucket = tencentcloud_cos_bucket.shop_bucket.id
acl = "private"
key = local.example_name
source = local.example_file
}

resource "tencentcloud_cdn_domain" "shop_cdn" {
depends_on = [null_resource.check_verification]
domain = local.domain
service_type = "web"
area = "mainland"
cache_key {
full_url_cache = "off"
}

origin {
origin_type = "cos"
origin_list = ["${tencentcloud_cos_bucket.shop_bucket.id}.cos-website.${local.region}.myqcloud.com"]
server_name = "${tencentcloud_cos_bucket.shop_bucket.id}.cos-website.${local.region}.myqcloud.com"
origin_pull_protocol = "follow"
cos_private_access = "on"
}

rule_cache {
cache_time = 86400
rule_type = "all"
rule_paths = ["*"]
switch = "on"
re_validate = "on"
}

compression {
switch = "on"
compression_rules {
algorithms = ["gzip"]
compress = true
max_length = 2097152
min_length = 256
rule_paths = ["js", "html", "css", "xml", "shtml", "htm", "json"]
rule_type = "file"
}
}

https_config {
https_switch = "on"
http2_switch = "on"
force_redirect {
switch = "on"
redirect_type = "https"
redirect_status_code = 302
}
ocsp_stapling_switch = "on"
server_certificate_config {
message = "自上传证书"
certificate_content = local.cert
private_key = local.private_key
}
verify_client = "off"
}
}

output "manage_url" {
value = "https://cosbrowser.cloud.tencent.com/editor?bucket=${tencentcloud_cos_bucket.shop_bucket.id}&region=ap-shanghai"
description = "存储桶文件管理地址"
}

output "dns_cname" {
value = tencentcloud_cdn_domain.shop_cdn.cname
description = "域名cname解析内容"
}

output "wxapp_url" {
value = "https://developers.weixin.qq.com/s/KMyOSEmp7MN7"
description = "微信小程序代码片段"
}

output "scan_url" {
value = "https://${local.domain}/${local.example_id}"
description = "在小程序后台配置二维码扫描后,将此路径制作成二维码,用微信扫码观看效果"
}

在 tf 文件同级目录下,还有几个配套的文件:

  1. index.html: 放置在对象存储桶里,用于扫码的中转页(非微信客户端扫码时展示提示)

  2. set.json: 一个商品信息的示例,你可以根据自己的需要配合小程序自行修改

  3. ssl.key: 自定义域名证书 key 文件

  4. ssl.crt: 自定义域名证书 crt 文件

除了两个证书文件需要自己提供以外,其他两个的文件内容如下:

index.html

<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>中间页面</title>
</head>
<body>
<div style="margin-top: 100px;text-align: center;">请使用微信客户端扫描此二维码</div>
</body>
</html>

set.json

{
"title": "家宴高端预制菜礼盒",
"desc": "理想中开发食品有限公司",
"price": {
"p": "¥98.00-208.00",
"t": "建议零售价"
},
"list": [{
"label":"规格",
"value":"370 × 170 × 280 mm"
},{
"label":"菜品",
"value":"胡椒猪肚鸡、毛血旺、红烧肉、酸汤肥牛、水煮肉片、辣子鸡丁、金汤肥牛、鱼香肉丝、牛肉煲、木须肉、糖醋里脊、宫保鸡丁、藤椒鲜炒鸡、菠萝咕咾肉、金汤酸菜鱼",
}]
}

接下来运行 terraform,具体使用参考腾讯云文档

https://cloud.tencent.com/document/product/1653/82867

由于是第一次讲述 terraform 代码配置,因此在这里特意列下执行过程,后面的架构文章就不再介绍了。

如果你想本地运行,需要在腾讯云的权限管控中获取 SK 信息,并写入环境变量:

export TENCENTCLOUD_SECRET_ID=YOUR_SECRET_ID
export TENCENTCLOUD_SECRET_KEY=YOUR_SECRET_KEY

在目录中运行终端,执行 init 命令,安装 provider plugins

terraform init

完成后,运行 plan 命令查看执行计划

terraform plan -out="coscdn" // 将计划导出到执行目录中,文件名coscdn

我们可以清晰的在计划中看到我们要做什么,新增、更新、移除资源的数量,以及拟输出的内容等等。

确认执行的内容后,我们可以用 apply 来执行

terraform apply "coscdn" // 这里的 coscdn 是 plan 时保存的

执行过程包含,开通对象存储桶,配置 policy,上传 2 个测试文件,开通 CDN 共 5 个部分

最后输出了几个内容(这些内容都是 tf 中定义的,后面其他案例会不一样,每个案例都会描述这一部分):

  1. dns_cname: 自定义域名应该 cname 解析的值,由于自定义域名可能来自不同的服务商,因此没有直接写到tf中,需要手动配置这个部分,如果你的域名也在腾讯云上,可以直接加配置解析这一部分。

  2. manage_url: 可以访问该地址,登录腾讯云账号来管理对象存储的内容。

后面商家自己可以创建同样的文件,自己维护里面的内容
  1. scan_url: 根据自定义域名做的二维码解析,以域名+商品 ID 的形式出现。由于在对象存储中配置了 error 页均为 index.html,因此除了 asset 下信息文件路径正常返回内容外,其余的所有路径均返回 index.html。这个是开发设计如此,你可以根据自己需要更改这一部分。

  2. wxapp_url: 这个是微信小程序代码片段链接,复制到浏览器打开后,会引导你安装或打开微信开发者工具 IDE,里面有相关的代码,接下来我们重点介绍这一部分。

如果在执行过程中报出了下述错误,需要先做域名 TXT 解析验证,验证域名所有权归属于你。

微信小程序开发调试

微信小程序的调试开发比较简单,使用代码片段导入后,打开 page/index/index.js 文件,将前 3 行代码做一些调整,改为自己的域名。

另外微信小程序扫普通二维码配置需要前往微信公众平台(mp.weixin.qq.com),在 开发配置 中按照规范配置内容:

微信开发者工具IDE中,可以通过配置参数 q=https%3A%2F%2Fwww.example.com%2F0 来模拟扫码 https://www.example.com/0 进入小程序。(URL 需要通过encodeURIComponent方法处理)

运行效果如下:

为什么要用 IaC

有部分人可能不太理解和适应 terraform 的表达形式,觉得用控制台的截图配文字说明更直观。

我之前给别人方案时,无论我说的怎样详实,在每个人的理解中也会有很多差异。即使如严谨的法律条文,不同的情景下也有不同的解释结果。

我们需要开通两个产品:对象存储、CDN 服务。CDN 服务需要配置 SSL 证书,并将源站指向对象存储桶。对象存储的 ACL 设置为私有读写,但要开鉴权给 CDN 服务。

大家有没有发现,我上面 balabala 说了一大堆,很多关键点都没说清,比如 CDN 服务的缓存规则具体怎么配置、自定义域名如何解析,应该解析什么内容、对象存储是否要开启静态网站能力、CDN 对 COS 开启鉴权应该怎么配置 Policy。

为了把这些表述清楚,我可能要配很多控制台截图或者操作动图。但大家在按方抓药时难免也有遗漏,这种实践的展现在传输时很多信息会丢失,复现成功率很低。

另外如果云厂商的控制台大改版,之前所有的截图基本就全废了。虽然我是腾讯云的资深用户,我也在架构方案中频繁提及腾讯云,但我并不认为腾讯云的文档和实践指引就是合格的。

我翻了翻腾讯云的 CDN 快速入门指引,看到的是这种内容:

而好巧不巧的是,这两天腾讯云控制台大改版。CDN 控制台内部的表格顺序也发生变化了,要不是深度结合上下文,一路连蒙带猜,很多人都不知道应该点什么。

这种带控制台截图的确实有用,可以给不懂操作的用户一步步指导(但截图并不是唯一的形式,也有更多更直观有效的展现形式给用户教学)。而且在每一次控制台页面内容发生变化后,都需要产品同学来第一时间更新维护才行,过时的截图对新手用户作用是负数。

尤其对于我这种带场景的架构实践,大家一定会想一比一复刻我的实践,在其基础上做一些理解和自由变更。这种前提基础上,我就必然会选择用一些更准确的表现形式,即使大家不想用 terraform,也可以根据其中的代码细节,得到我每个云产品的配置信息和前后关系。

写在后面

上面展示的案例代码简化了很多,实际上还有轮播图和菜品图片。目前该商家小程序上线已经小半年了,这半年总花费约 5 块钱,平均单月价格 0.78 元,相比买服务器是很便宜了。

在帮助企业组织来做云服务的架构设计过程中,我发现大部分企业技术决策者,对如何做云计算架构设计没有一点经验,还是按照 IDC 时代的主机角色来看待架构问题。

而各大云计算厂商好像也不太重视云计算推荐架构的案例推广。案例全是大企业花重金采购的一个单一产品这类的,这对要上云的大部分用户没有任何参考价值,致使大部分的潜在上云用户的认知是严重失衡的。

因此,我计划不断探索和整理一些有意义且可复制的案例,帮助更多潜在上云用户理解和获得云服务的价值既然各大云服务商对此类案例不够重视,那我们就自己来填补这一空白。

关于作者:

ZiraLi,微信生态领域 MVP,就职于相关团队担任技术产品经理、架构师;为多家企业组织提供上云架构和微信生态产品咨询服务。


继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存