前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >操作指南:通过Rancher在K8S上运行PostgreSQL数据库

操作指南:通过Rancher在K8S上运行PostgreSQL数据库

原创
作者头像
Portworx
修改于 2020-02-06 12:23:52
修改于 2020-02-06 12:23:52
3.2K00
代码可运行
举报
运行总次数:0
代码可运行

通过Rancher Kubernetes Engine运行高可用 PostgreSQL

这篇是我们关于在Kubernetes上运行PostgreSQL系列文章的其中一篇。下面是相关文章和链接。

  • 在亚马逊AWS Elastic Container Service for Kubernetes (EKS) 上运行高可用 PostgreSQL:

(https://portworx.com/postgresql-amazon-eks/)

  • 在微软Azure Kubernetes Service (AKS) 上运行高可用 PostgreSQL:

(https://portworx.com/ha-postgresql-azure-aks/)

  • 在Google Kubernetes Engine (GKE) 上运行高可用 PostgreSQL:

(https://portworx.com/run-ha-postgresql-gke/)

  • 在RedHat OpenShift 上运行高可用 PostgreSQL:

(https://portworx.com/run-ha-postgresql-red-hat-openshift/)

  • 在IBM Cloud Kubernetes Service (IKS) 上运行高可用 PostgreSQL:

(https://portworx.com/run-ha-postgresql-ibm-cloud-kubernetes-service/)

  • 在IBM 私有云上运行高可用 PostgreSQL:

(https://portworx.com/run-ha-postgresql-ibm-cloud-private/)

Rancher Kubernetes Engine (RKE)是一个轻量级的Kubernetes 安装程序,支持在裸金属和虚拟机上安装Kubernetes。RKE解决了Kubernetes安装的复杂性问题。通过RKE安装是比较简单的,而跟下层的操作系统无关。

Portworx是一个云原生的存储和数据管理平台,来支撑Kubernetes上持久性的工作负载。通过Portworx,用户能够管理不同基础架构上的、不同容器调度器上的数据库。它为所有的有状态服务(Stateful Service)提供了一个单一的数据管理层。

本文列出了操作步骤:通过RancherKubernetes Engine (RKE),在AWS的Kubernetes集群上,部署和管理高可用PostgreSQL集群。

总结来说,在Amazon上运行高可用PostgreSQL,需要:

  1.   通过Rancher KubernetesEngine安装一个Kubernetes集群
  2.  安装云原生存储解决方案Portworx,作为Kubernetes的一个DaemonSet。
  3.   建立一个存储类来定义你的存储要求,比如,复制因子,快照策略和性能情况
  4.  使用Kubernetes部署PostgreSQL
  5.  通过killing或者cordoning集群中的节点,来测试故障恢复
  6. 可能的话,动态的调整PG Volume的大小,快照和备份Postgres到S3

如何通过RKE来创建一个Kubernetes集群

RKE是一个安装和配置Kubernetes的工具。可以支持的环境包括裸金属,虚拟机或者IaaS。在本文中,我们会在AWS EC2上创建一个3节点的Kubernetes集群。

更为详细的步骤,可以参考这篇tutorial from The New Stack. (https://thenewstack.io/run-stateful-containerized-workloads-with-rancher-kubernetes-engine-and-portworx/)

做完这些操作,我们会创建一个1 master 和 3 worker 节点的集群。

在Kubernetes上安装Portworx

在RKE的Kubernetes 上安装Portworx,跟在Kubernetes集群上通过Kops安装没什么不同。Portworx有详细的文档,列出每步的操作 (https://docs.portworx.com/portworx-install-with-kubernetes/cloud/aws/),来完成在AWS环境的Kubernetes上运行Portworx集群。

The New Stacktutorial(https://thenewstack.io/run-stateful-containerized-workloads-with-rancher-kubernetes-engine-and-portworx/) 也包含了在Kubernetes部署Portworx DaemonSet的所有操作步骤。

Kubernetes集群运行起来,Portworx安装和配置完成,我们就开始部署一个高可用的PostgreSQL数据库

创建一个Postgres 存储类

通过存储类对象,一个Admin可以定义集群中不同的Portworx卷的类。这些类在动态的卷的部署过程中会被用到。存储类本身定义了复制因子,IO情况(例如数据库或者CMS),以及优先级(比如SSD或者HDD)。这些参数影响着工作负载的可用性和输出,因此参数可以被根据每个卷分别设置。这很重要,因为对生产系统的数据库的要求,跟研发测试系统是完全不一样的。

在下面的例子里,我们部署的存储类,它的复制因子是3,IO情况设定成“db”,优先级设定成“high”。这意味着存储会被优化为适合低传输速率的数据库负载(Postgres),并且自动的部署在集群具备最高性能的存储里。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl create -f https://raw.githubusercontent.com/fmrtl73/katacoda-scenarios-1/master/px-k8s-postgres-all-in-one/assets/px-repl3-sc.yaml

storageclass "px-repl3-sc" created

创建一个Postgres PVC

我们现在可以基于存储类创建一个PersistentVolume Claim (PVC)。动态部署的优势就在于,claims能够在不需要显性部署持久卷Persistent Volume (PV)的情况下被创建。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl create -f https://raw.githubusercontent.com/fmrtl73/katacoda-scenarios-1/master/px-k8s-postgres-all-in-one/assets/px-postgres-pvc.yaml

persistentvolumeclaim "px-postgres-pvc" created

PostgreSQL的密码会被创建成Secret。运行下面的命令来用正确的格式创建Secret。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ echo postgres123 > password.txt
$ tr -d '\n' .strippedpassword.txt && mv .strippedpassword.txt password.txt
$ kubectl create secret generic postgres-pass --from-file=password.txt
secret "postgres-pass" created

在Kubernetes上部署PostgreSQL

最后,让我们创建一个PostgreSQL实例,作为一个Kubernetes部署对象。为了简单起见,我们只部署一个单独的Postgres Pod。因为Portworx提供同步复制来达到高可用。因此一个单独的Postgres实例,是Postgres数据库的最佳部署方式。Portworx也支持多节点的Postgres部署方式,看你的需要。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl create -f https://raw.githubusercontent.com/fmrtl73/katacoda-scenarios-1/master/px-k8s-postgres-all-in-one/assets/postgres-app.yaml

deployment "postgres" created

确保Postgres的Pods是在运行的状态。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl get pods -l app=postgres -o wide --watch

等候直到Postgres pod变成运行状态。

我们可以通过使用与PostgresPod一起运行的pxctl工具,来检查Portworx卷。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ VOL=`kubectl get pvc | grep px-postgres-pvc | awk '{print $3}'`
$ PX_POD=$(kubectl get pods -l name=portworx -n kube-system -o jsonpath='{.items[0].metadata.name}')
$ kubectl exec -it $PX_POD -n kube-system -- /opt/pwx/bin/pxctl volume inspect ${VOL}

命令的输出信息,确认了支撑PostgreSQL数据库实例的卷已经被创建完成了。

PostgreSQL的错误恢复

让我们为数据库填充5百万行的样例数据。

我们首先找到运行PostgreSQL的Pod,来访问shell。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ POD=`kubectl get pods -l app=postgres | grep Running | grep 1/1 | awk '{print $1}'`
$ kubectl exec -it $POD bash

现在我们进入了Pod,我们能够连接到Postgres并且创建数据库。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# psql
pgbench=# create database pxdemo;
pgbench=# \l
pgbench=# \q

默认状态下,Pgbench会创建4张表:(pgbench_branches,pgbench_tellers,pgbench_accounts,pgbench_history),在主pgbench_accounts表里会有10万行。这样我们创建了一个简单的16MB大小的数据库。

使用-s选项, 我们可以增加在每张表中的行的数量。在上面的命令中,我们在“scaling”上填写了50,这样pgbench就会创建一个50倍默认大小的数据库。

我们的pgbench_accounts现在有5百万行了。这样我们的数据库变成了800MB (50*16MB)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# pgbench -i -s 50 pxdemo;

等待直到pgbench完成表的创建。我们接着来确认一下

pgbench_accounts现在有500万行的填充。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# psql pxdemo
\dt
select count(*) from pgbench_accounts;
\q
exit

现在,我们来模拟PostgreSQL正在运行的节点的失效,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ NODE=`kubectl get pods -l app=postgres -o wide | grep -v NAME | awk '{print $7}'`
$ kubectl cordon ${NODE}

node "ip-172-20-57-55.ap-southeast-1.compute.internal" cordoned

执行kubectl get nods, 确认了其中一个节点的排程已经失效了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl get nodes

我们继续删除这个PostgreSQLpod。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ POD=`kubectl get pods -l app=postgres -o wide | grep -v NAME | awk '{print $1}'`
$ kubectl delete pod ${POD}

pod "postgres-556994cbd4-b6ghn" deleted

一旦删除完成。Portworx STorageORchestrator for Kubernetes (STORK)(https://portworx.com/stork-storage-orchestration-kubernetes/),会把pod重置来创建有数据复制集的节点。

一旦Pod被删除,它会被重置到有数据复制集的节点上。Portworx STorageORchestrator for Kubernetes (STORK) (https://portworx.com/stork-storage-orchestration-kubernetes/)- Portworx的客户存储排程器,允许在数据所在节点上放置多个pod,并且确保正确的节点能够被选择来用来排程Pod。

让我们运行下面的命令验证一下。我们会发现一个新的pod被创建了,并且被排程在了一个不同的节点上。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl get pods -l app=postgres

让我们把之前的节点重新部署回来。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl uncordon ${NODE}

node "ip-172-20-57-55.ap-southeast-1.compute.internal" uncordoned

最后,我们验证一下数据仍然是可用的。

我们来看下容器里的pod名称和exec。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ POD=`kubectl get pods -l app=postgres | grep Running | grep 1/1 | awk '{print $1}'`
$ kubectl exec -it $POD bash

现在用psql来确保我们的数据还在。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# psql pxdemo
pxdemo=# \dt
pxdemo=# select count(*) from pgbench_accounts;
pxdemo=# \q
pxdemo=# exit

我们看到数据库表都还在,并且所有的内容都是正确的。

在Postgres进行存储管理

测试了端到端的数据库错误恢复后,我们在Kubernetes集群上来运行StorageOps。

完全无停机下,扩充卷

我们现在来演示一下,在空间将满的情况下,如何简单的、动态的为卷添加空间。

在容器内打开一个shell,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ POD=`kubectl get pods -l app=postgres | grep Running | awk '{print $1}'`
$ kubectl exec -it $POD bash

让我们来用pgbench来运行一个baseline transaction benchmark,它将尝试增加卷容量到1Gib,并且没能成功。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ pgbench -c 10 -j 2 -t 10000 pxdemo
$ exit

在运行上面命令的时候,可能会有多种错误产生。第一个错误提示Pod已经没有空间了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
PANIC: could not write to file "pg_xlog/xlogtemp.73": No space left on device

Kubernetes并不支持在PVC创建后进行修改。我们在Portworx上用pxctl CLI工具来进行操作。

我们来获取卷的名称,用pxctl工具来查看。

SSH到节点里,运行下面的命令

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
POD=`/opt/pwx/bin/pxctl volume list --label pvc=px-postgres-pvc | grep -v ID | awk '{print $1}'`

$ /opt/pwx/bin/pxctl v i $POD

注意到卷还有10%就要满了。让我们用下面的命令来扩充。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ /opt/pwx/bin/pxctl volume update $POD --size=2

Update Volume: Volume update successful for volume 834897770479704521

为卷做快照,并且恢复数据库

Portworx支持为Kubernetes PVCs创建快照。让我们为之前创建的Postgres PVC来创建一个快照。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl create -f https://github.com/fmrtl73/katacoda-scenarios-1/raw/master/px-k8s-postgres-all-in-one/assets/px-snap.yaml

volumesnapshot "px-postgres-snapshot" created

可以通过下面的命令来看所有的快照。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl get volumesnapshot,volumesnapshotdata

有了快照,我们来删掉数据库。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ POD=`kubectl get pods -l app=postgres | grep Running | grep 1/1 | awk '{print $1}'`
$ kubectl exec -it $POD bash
$ psql
drop database pxdemo;
\l
\q
exit

快照就跟卷是一样的,我们可以使用它来创建一个新的PostgreSQL实例。让我们恢复快照数据,来创建一个新的PostgreSQL实例。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl create -f https://raw.githubusercontent.com/fmrtl73/katacoda-scenarios-1/master/px-k8s-postgres-all-in-one/assets/px-snap-pvc.yaml

persistentvolumeclaim "px-postgres-snap-clone" created

从新的PVC,我们创建一个PostgreSQL Pod,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl create -f https://raw.githubusercontent.com/fmrtl73/katacoda-scenarios-1/master/px-k8s-postgres-all-in-one/assets/postgres-app-restore.yaml

deployment "postgres-snap" created

确认这个pod是在运行状态。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ kubectl get pods -l app=postgres-snap

最后,让我们访问由benchmark工具创建的数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ POD=`kubectl get pods -l app=postgres-snap | grep Running | grep 1/1 | awk '{print $1}'`
$ kubectl exec -it $POD bash
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ psql pxdemo
\dt
select count(*) from pgbench_accounts;
\q
exit

我们发现表和数据都是正常的。如果我们想要在另一个Amazon区域创建一个容灾备份,我们可以把快照推送到Amazon S3。Portworx快照支持所有的S3兼容存储对象,所以备份也可以是其他的云或者是本地部署的数据中心

_

小结

Portworx可以通过RKE很容易的部署,用来运行Kubernetes上生产系统中有状态的工作负载。通过跟STORK的整合,DevOps和StorageOps团队能够无缝的在Kubernetes上运行数据库集群。他们也可以为云原生应用运行传统的操作,比如扩充卷,快照,备份,容灾恢复。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何跨不同版本K8S,为有状态工作负载做蓝绿部署
容器的生态正在爆发!不仅仅应用层在快速变化,还有用于管理应用程序的平台:Kubernetes,也在快速变化。这就为Ops团队带来了一个必须要解决的难题。IT团队如何才能保证一款应用程序能够在各种不同版本的Kubernetes上都能良好运行呢?
Portworx
2019/12/02
1.7K0
如何为K8S生产系统配置安全管理?
PX-Security:针对Kubernetes持久卷的多租户授权、身份验证和RBAC
Portworx
2020/02/04
1.4K0
如何在两个OpenShift集群间迁移有状态应用
Portworx是一个支撑K8S有状态应用的持久存储和数据管理平台。通过Portworx,它为有状态应用提供了一个单一的数据管理层,从而用户可以在任何底层架构上运行类似数据库这样的有状态应用。
Portworx
2020/06/29
1.1K0
操作指南:通过 OpenShfit 运行高可用 MySQL数据库
我们的文章包括了MySQL on Kubernetes在不同平台不同场景下的情况。相关文章的列表如下:
Portworx
2020/02/25
11.3K0
如何在K8S上备份和恢复MySQL
越来越多的生产系统和关键应用运行在K8S上。在生产系统运行有状态应用,并不是一件容易的事情,它需要我们仔细的计划并部署。我们之前有一篇文章专门介绍如何在K8S上运行高可用的MySQL。这次我们来介绍下如何备份和恢复MySQL。当我们在生产环境中备份和恢复MySQL,我们需要思考下面的问题:
Portworx
2020/07/26
2.9K0
Portworx演示:在K8S集群间迁移有状态的应用和数据
越来越多的企业选择Kubernetes作为基础架构,它能够帮助我们缩短软件项目上市时间、降低基础架构成本、并提高软件质量。由于Kubernetes比较新,因此IT团队都在学习如何在生产环境中,在Kubernetes上对应用程序进行运行和维护。本文将探讨,当在需要额外的计算能力时,将Kubernetes应用程序迁移至另一个新的集群。
Portworx
2019/11/14
2.5K0
Portworx Essential上手操作指南
Kubernetes架构对于初学者来说还是比较复杂,尤其是在Kubernetes上运行有状态应用,有时用户还并不需要企业级规模的功能。因此Portworx发布Portworx Essentials版本,它为小型生产环境提供了所有必要的Kubernetes存储功能,而且是永久免费的。
Portworx
2020/04/29
1.1K0
Portworx Essentials + Velero:备份Kubernetes应用
Portworx近期发布了免费的Essentials版本。(https://portworx.com/announcing-portworx-essentials/)。Essentias)。
Portworx
2020/05/08
1.3K0
Rancher2 & K8S部署踩坑记录
如果服务器上下载不了kubectl可去github release页面下载最新版kubectl,再上传至服务器
Ewdager
2020/07/14
3.9K0
Rancher2 & K8S部署踩坑记录
K3S 入门级实战教程,和 K8S 有何不同?
我们知道 Kubernetes(或 K8s)是用于管理容器化工作负载和服务的便携式开源平台。它有助于在集群中编排和管理 pod 中的应用程序。它还有效地改进了任务,例如重新部署的零停机时间或容器的自我修复。
我的小碗汤
2023/03/20
1.4K0
K3S 入门级实战教程,和 K8S 有何不同?
红队视角出发的k8s敏感信息收集——持久化存储与数据泄露
在Kubernetes集群中,持久化存储卷如同数据的保险箱,承载着应用运行所必需的各类敏感信息。然而,从红队视角出发,这些存储卷也可能成为攻击者觊觎的目标。通过巧妙地利用配置不当或已知漏洞,攻击者能够从中收集到包括密钥、访问凭证在内的大量敏感数据,进而导致数据泄露事件的发生。
zhouzhou的奇妙编程
2025/02/07
2000
K8S数据迁移方法
Kubernetes改变了我们所有人对计算平台的看法。我们同样也需要改变现代应用程序存储数据的方式。企业越来越多地依赖数字服务来接触客户,传统企业正在Kubernetes上重新部署它们的IT应用和服务。容器的可移植性和Kubernetes自动化的好处意味着在整个IT开发/测试和生产生命周期中我们可以更快、更可靠地交付应用程序。与此同时,企业必须认识到多云部署不仅仅是一种供应策略,而且还是一种对客户最合理的应用程序交付方式。
Portworx
2019/11/29
2.1K0
K8s生产环境排错之:那些暗黑操作
凌晨2点收到告警,某个生产环境Pod疯狂重启但毫无日志输出。官方文档的kubectl describe三板斧毫无作用,直到偶然发现...
Jimaks
2025/04/22
1160
云原生 Kubernetes 分布式存储平台 Longhorn 初体验
前面我们学习了本地存储、NFS共享存储,除了这些存储类型之外,还有一个块存储,同样为 Kubernetes 提供块存储的方案有很多,比如 Ceph RBD,今天我们为大家介绍的是 Rancher 开源的一款 Kubernetes 的云原生分布式块存储方案 - Longhorn,Longhorn 是一个轻量级且功能强大的云原生 Kubernetes 分布式存储平台,可以在任意基础设施上运行,Longhorn 还可以与 Rancher 结合使用,将帮助你在 Kubernetes 环境中轻松、快速和可靠地部署高可用性持久化块存储。
我是阳明
2022/04/06
1.7K0
云原生 Kubernetes 分布式存储平台 Longhorn 初体验
pgpool-II 4.3 中文手册 - 入门教程
在本节中,我们假设您已经安装了 Pgpool-II 与 PostgreSQL 集群。
为少
2022/05/17
1.9K0
pgpool-II 4.3 中文手册 - 入门教程
k8s(5)-kubernetes存储系统Volume和PV
K8S的存储系统从基础到高级又大致分为三个层次:普通Volume,Persistent Volume 和动态存储供应。
黄规速
2023/03/06
1.6K0
k8s(5)-kubernetes存储系统Volume和PV
认识Kubernates(K8S)
在后端开发中,在介绍Jenkins的可伸缩部署方式上,主要有两种方式:一种是基于Docker(或者docker-swarm 集群)的部署方式,另外一种是基于kubernetes的部署方式(而kubernetes也是基于docker而设计的)。 基本概念 kubernetes是google开源的容器集群管理系统,提供应用部署、维护、扩展机制等功能,利用kubernetes能方便管理跨集群运行容器化的应用,简称:k8s。 基本概念 Pod:若干相关容器的组合,Pod包含的容器运行在同一host上,这些容器使用相
xiangzhihong
2018/02/06
2.8K0
认识Kubernates(K8S)
Postgresql 数据库导入导出 物理VS逻辑 集合
PostgreSQL 数据的导入导出本身并没有特别高的技术要求,属于日常操作,但熟悉导入导出以及选择数据导入导出的方式还是有点思考空间的。怎么导出数据的方式更稳妥,更适应业务的需求。下面就先总结数据导入导出中的数据导出的一部分方式和命令的实例,其中一些也是我在总结中发现的,例如COPY 的方式还可以有加速的方式,有时候觉得简单,和简单是两码事。
AustinDatabases
2021/12/06
1.9K0
Postgresql 数据库导入导出  物理VS逻辑 集合
k8s实践(12)--K8s service服务详解
Kubernetes Pod 是有生命周期的,它们可以被创建,也可以被销毁,然而一旦被销毁生命就永远结束。 通过 ReplicationController 能够动态地创建和销毁 Pod(例如,需要进行扩缩容,或者执行)。 每个 滚动升级 Pod 都会获取它自己的 IP 地址,即使这些 IP 地址不总是稳定可依赖的。 这会导致一个问题:在 Kubernetes 集群中,如果一组 Pod(称为 backend)为其它 Pod (称为 frontend)提供服务,那么那些 frontend 该如何发现,并连接到这组 Pod 中的哪些 backend 呢?
黄规速
2022/04/14
9.2K0
k8s实践(12)--K8s service服务详解
K8s——数据持久化
数据的持久化一直都是需要我们非常关心的问题,docker如此,K8s也不例外。在k8s中,有一个数据卷的概念。
小手冰凉
2020/09/11
2.2K0
相关推荐
如何跨不同版本K8S,为有状态工作负载做蓝绿部署
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验