第 7 部分。高可用性
Linux 下的集群¶
高可用性是 IT 中经常使用的一个术语,与系统架构或服务有关,用于指定此架构或服务具有合适的可用性率。~ 维基百科
此可用性是性能指标,以百分比表示,通过运行时间 / 总期望运行时间的比率获得。
比率 | 年度停机时间 |
---|---|
90% | 876 小时 |
95% | 438 小时 |
99% | 87 小时 36 分钟 |
99,9% | 8 小时 45 分钟 36 秒 |
99,99% | 52 分钟 33 秒 |
99,999% | 5 分钟 15 秒 |
99,9999% | 31.68 秒 |
"高可用性" (HA) 指为保证服务尽可能高的可用性而采取的所有措施,即每天 24 小时正常运行。
概述¶
集群是一个“计算机集群”,是由两个或多个机器组成的集合。
集群允许
- 分布式计算,通过利用所有节点的计算能力
- 高可用性:在节点故障时保证服务连续性和自动服务故障转移
服务类型¶
主动/被动服务
使用 Pacemaker 和 DRBD 安装具有两个主动/被动节点的集群是许多需要高可用性系统的低成本解决方案。
N+1 服务
使用多个节点,Pacemaker 可以通过允许多个主动/被动集群组合并共享备份节点来降低硬件成本。
N 到 N 服务
使用共享存储,每个节点都可能用于容错。Pacemaker 还可以运行服务的多个副本以分散工作负载。
远程站点服务
Pacemaker 包含增强功能,简化了多站点集群的创建。
VIP¶
VIP 是分配给主动/被动集群的虚拟 IP 地址。将 VIP 分配给主动集群节点。如果服务发生故障,VIP 会在故障节点上停用,同时在接管的节点上激活。这被称为故障转移。
客户端始终使用 VIP 访问集群,从而使主动服务器故障转移变得透明。
脑裂¶
脑裂是集群可能遇到的主要风险。当集群中的多个节点认为其邻居处于非活动状态时,就会发生这种情况。然后,节点尝试启动冗余服务,多个节点提供相同服务,这会导致令人讨厌的副作用(网络上的重复 VIP、竞争数据访问等)。
避免此问题的可能技术解决方案是
- 将公共网络流量与集群网络流量分开
- 使用网络绑定
Pacemaker (PCS)¶
在本章中,您将了解 Pacemaker,这是一种集群解决方案。
目标:您将学习如何
安装和配置 Pacemaker 集群;
管理 Pacemaker 集群。
集群、ha、高可用性、pcs、pacemaker
知识:
复杂度:
阅读时间:20 分钟
概论¶
Pacemaker 是集群的软件部分,负责管理其资源(VIP、服务、数据)。它负责启动、停止和监督集群资源。它保证了节点的高可用性。
Pacemaker 使用由corosync(默认)或Heartbeat提供的消息层。
Pacemaker 由5 个关键组件组成
- 集群信息库 (CIB)
- 集群资源管理守护进程 (CRMd)
- 本地资源管理守护进程 (LRMd)
- 策略引擎 (PEngine 或 PE)
- 隔离守护进程 (STONITHd)
CIB 代表集群配置和所有集群资源的当前状态。其内容在整个集群中自动同步,并由 PEngine 用于计算如何实现理想的集群状态。
然后将指令列表提供给指定控制器 (DC)。Pacemaker 通过选举一个 CRMd 实例作为主节点来集中所有集群决策。
DC 按要求的顺序执行 PEngine 的指令,并通过 Corosync 或 Heartbeat 将它们传输到本地 LRMd 或其他节点的 CRMd。
有时,可能需要停止节点以保护共享数据或启用恢复。Pacemaker 附带了 STONITHd 用于此目的。
Stonith¶
Stonith 是 Pacemaker 的一个组件。它代表“Shoot-The-Other-Node-In-The-Head”,这是确保尽快隔离故障节点(关闭或至少与共享资源断开连接)的推荐做法,从而避免数据损坏。
无响应节点并不意味着它无法再访问数据。在将数据移交给另一个节点之前,确保节点不再访问数据的唯一方法是使用 STONITH,它将关闭或重新启动故障服务器。
如果集群服务无法关闭,STONITH 也会发挥作用。在这种情况下,Pacemaker 使用 STONITH 强制整个节点停止。
仲裁管理¶
仲裁代表运作中的节点的最小数量,以验证决策,例如决定当其中一个节点出错时哪个备份节点应该接管。默认情况下,Pacemaker 要求超过一半的节点在线。
当通信问题将集群分成几个组节点时,仲裁会阻止资源启动到超过预期数量的节点上。当超过一半已知在线的节点都在其组中时,集群处于仲裁状态(active_nodes_group > active_total_nodes / 2)。
当仲裁未达到时,默认决定是关闭所有资源。
案例研究
- 在**两个节点的集群**中,由于无法达到仲裁,因此必须忽略节点故障,否则整个集群将关闭。
- 如果一个 5 节点集群被分成 3 节点和 2 节点的 2 个组,3 节点组将拥有仲裁并继续管理资源。
- 如果一个 6 节点集群被分成 3 个节点的 2 个组,则没有一个组会拥有仲裁。在这种情况下,Pacemaker 的默认行为是停止所有资源以避免数据损坏。
集群通信¶
Pacemaker 使用**Corosync** 或**Heartbeat**(来自 Linux-ha 项目)进行节点间通信和集群管理。
Corosync¶
**Corosync 集群引擎**是集群成员之间的消息传递层,它集成了额外的功能来在应用程序中实现高可用性。Corosync 来源于 OpenAIS 项目。
节点使用 UDP 协议以客户端/服务器模式进行通信。
它可以管理超过 16 个主动/被动或主动/主动模式的集群。
Heartbeat¶
Heartbeat 技术比 Corosync 更有限。不可能创建超过两个节点的集群,而且其管理规则不如其竞争对手复杂。
注意
今天,选择 pacemaker/corosync 似乎更合适,因为它是在 RedHat、Debian 和 Ubuntu 发行版中的默认选择。
数据管理¶
DRDB 网络 RAID¶
DRDB 是一种块类型设备驱动程序,它使能够在网络上实现 RAID 1(镜像)。
当 NAS 或 SAN 技术不可用但需要数据同步时,DRDB 会很有用。
安装¶
要安装 Pacemaker,首先启用 highavailability
存储库。
sudo dnf config-manager --set-enabled highavailability
关于 pacemaker 软件包的一些信息
$ dnf info pacemaker
Rocky Linux 9 - High Availability 289 kB/s | 250 kB 00:00
Available Packages
Name : pacemaker
Version : 2.1.7
Release : 5.el9_4
Architecture : x86_64
Size : 465 k
Source : pacemaker-2.1.7-5.el9_4.src.rpm
Repository : highavailability
Summary : Scalable High-Availability cluster resource manager
URL : https://www.clusterlabs.org/
License : GPL-2.0-or-later AND LGPL-2.1-or-later
Description : Pacemaker is an advanced, scalable High-Availability cluster resource
: manager.
:
: It supports more than 16 node clusters with significant capabilities
: for managing resources and dependencies.
:
: It will run scripts at initialization, when machines go up or down,
: when related resources fail and can be configured to periodically check
: resource health.
:
: Available rpmbuild rebuild options:
: --with(out) : cibsecrets hardening nls pre_release profiling
: stonithd
使用 repoquery
命令,您可以找出 pacemaker 软件包的依赖项。
$ repoquery --requires pacemaker
corosync >= 3.1.1
pacemaker-cli = 2.1.7-5.el9_4
resource-agents
systemd
...
因此,pacemaker 安装将自动安装 corosync 和 pacemaker 的 CLI 接口。
关于 corosync 软件包的一些信息
$ dnf info corosync
Available Packages
Name : corosync
Version : 3.1.8
Release : 1.el9
Architecture : x86_64
Size : 262 k
Source : corosync-3.1.8-1.el9.src.rpm
Repository : highavailability
Summary : The Corosync Cluster Engine and Application Programming Interfaces
URL : http://corosync.github.io/corosync/
License : BSD
Description : This package contains the Corosync Cluster Engine Executive, several default
: APIs and libraries, default configuration files, and an init script.
现在安装所需的软件包
sudo dnf install pacemaker
如果您有防火墙,请打开它。
sudo firewall-cmd --permanent --add-service=high-availability
sudo firewall-cmd --reload
注意
现在不要启动服务,因为它们未配置,无法运行。
集群管理¶
pcs
软件包提供了集群管理工具。pcs
命令是用于管理**Pacemaker 高可用性堆栈**的命令行界面。
集群配置可以手动完成,但 pcs 软件包使管理(创建、配置和故障排除)集群变得更加容易!
注意
pcs 有替代方案。
在所有节点上安装软件包并激活守护进程
sudo dnf install pcs
sudo systemctl enable pcsd --now
软件包安装创建了一个 hacluster
用户,该用户具有空密码。要执行诸如同步 corosync 配置文件或重新引导远程节点等任务。为该用户分配密码是必要的。
hacluster:x:189:189:cluster user:/var/lib/pacemaker:/sbin/nologin
在所有节点上,为 hacluster 用户分配相同的密码
echo "pwdhacluster" | sudo passwd --stdin hacluster
注意
请将“pwdhacluster”替换为更安全的密码。
从任何节点,可以以 hacluster 用户身份在所有节点上进行身份验证,然后在这些节点上使用 pcs
命令
$ sudo pcs host auth server1 server2
Username: hacluster
Password:
server1: Authorized
server2: Authorized
从执行 pcs 身份验证的节点,启动集群配置
$ sudo pcs cluster setup mycluster server1 server2
No addresses specified for host 'server1', using 'server1'
No addresses specified for host 'server2', using 'server2'
Destroying cluster on hosts: 'server1', 'server2'...
server2: Successfully destroyed cluster
server1: Successfully destroyed cluster
Requesting remove 'pcsd settings' from 'server1', 'server2'
server1: successful removal of the file 'pcsd settings'
server2: successful removal of the file 'pcsd settings'
Sending 'corosync authkey', 'pacemaker authkey' to 'server1', 'server2'
server1: successful distribution of the file 'corosync authkey'
server1: successful distribution of the file 'pacemaker authkey'
server2: successful distribution of the file 'corosync authkey'
server2: successful distribution of the file 'pacemaker authkey'
Sending 'corosync.conf' to 'server1', 'server2'
server1: successful distribution of the file 'corosync.conf'
server2: successful distribution of the file 'corosync.conf'
Cluster has been successfully set up.
注意
pcs 集群设置命令处理两个节点集群的仲裁问题。因此,这种集群在两个节点之一出现故障时将正常运行。如果您手动配置 Corosync 或使用其他集群管理 shell,则必须正确配置 Corosync。
您现在可以启动集群
$ sudo pcs cluster start --all
server1: Starting Cluster...
server2: Starting Cluster...
启用集群服务,使其在启动时启动
sudo pcs cluster enable --all
检查服务状态
$ sudo pcs status
Cluster name: mycluster
WARNINGS:
No stonith devices and stonith-enabled is not false
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: server1 (version 2.1.7-5.el9_4-0f7f88312) - partition with quorum
* Last updated: Mon Jul 8 17:50:14 2024 on server1
* Last change: Mon Jul 8 17:50:00 2024 by hacluster via hacluster on server1
* 2 nodes configured
* 0 resource instances configured
Node List:
* Online: [ server1 server2 ]
Full List of Resources:
* No resources
Daemon Status:
corosync: active/disabled
pacemaker: active/disabled
pcsd: active/enabled
添加资源¶
在您配置资源之前,您需要处理警报消息
WARNINGS:
No stonith devices and stonith-enabled is not false
在此状态下,Pacemaker 将拒绝启动您的新资源。
您有两个选择
- 禁用
stonith
- 配置它
首先,您将禁用 stonith
,直到您学会如何配置它
sudo pcs property set stonith-enabled=false
警告
请注意,不要在生产环境中禁用 stonith
!
VIP 配置¶
您将在集群上创建的第一个资源是 VIP。
使用 pcs resource standards
命令列出可用的标准资源
$ pcs resource standards
lsb
ocf
service
systemd
此 VIP 对应于客户的 IP 地址,以便他们可以访问未来的集群服务。您必须将其分配给其中一个节点。然后,如果发生故障,集群将切换该资源,使其从一个节点切换到另一个节点,以确保服务的连续性。
pcs resource create myclusterVIP ocf:heartbeat:IPaddr2 ip=192.168.1.12 cidr_netmask=24 op monitor interval=30s
ocf:heartbeat:IPaddr2
参数包含三个字段,它们为 Pacemaker 提供以下信息
- 标准(此处为
ocf
) - 脚本命名空间(此处为
heartbeat
) - 资源脚本名称
结果是在已管理资源列表中添加了虚拟 IP 地址
$ sudo pcs status
Cluster name: mycluster
...
Cluster name: mycluster
Cluster Summary:
* Stack: corosync (Pacemaker is running)
...
* 2 nodes configured
* 1 resource instance configured
Full List of Resources:
* myclusterVIP (ocf:heartbeat:IPaddr2): Started server1
...
在这种情况下,VIP 在 server1 上处于活动状态。可以使用 ip
命令进行验证
$ ip add show dev enp0s3
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:df:29:09 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.10/24 brd 192.168.1.255 scope global noprefixroute enp0s3
valid_lft forever preferred_lft forever
inet 192.168.1.12/24 brd 192.168.1.255 scope global secondary enp0s3
valid_lft forever preferred_lft forever
切换测试¶
从网络上的任何位置,在 VIP 上运行 ping 命令
ping 192.168.1.12
将活动节点置于待机状态
sudo pcs node standby server1
检查在操作期间所有 ping 是否都成功(没有丢失的 icmp_seq
)
64 bytes from 192.168.1.12: icmp_seq=39 ttl=64 time=0.419 ms
64 bytes from 192.168.1.12: icmp_seq=40 ttl=64 time=0.043 ms
64 bytes from 192.168.1.12: icmp_seq=41 ttl=64 time=0.129 ms
64 bytes from 192.168.1.12: icmp_seq=42 ttl=64 time=0.074 ms
64 bytes from 192.168.1.12: icmp_seq=43 ttl=64 time=0.099 ms
64 bytes from 192.168.1.12: icmp_seq=44 ttl=64 time=0.044 ms
64 bytes from 192.168.1.12: icmp_seq=45 ttl=64 time=0.021 ms
64 bytes from 192.168.1.12: icmp_seq=46 ttl=64 time=0.058 ms
检查集群状态
$ sudo pcs status
Cluster name: mycluster
Cluster Summary:
...
* 2 nodes configured
* 1 resource instance configured
Node List:
* Node server1: standby
* Online: [ server2 ]
Full List of Resources:
* myclusterVIP (ocf:heartbeat:IPaddr2): Started server2
VIP 已移至 server2。像之前一样使用 ip add
命令进行检查。
将 server1 返回到池
sudo pcs node unstandby server1
注意
一旦 server1 unstandby
,集群将恢复到正常状态,但资源不会转移回 server1:它仍然停留在 server2 上。
服务配置¶
您将在集群的两个节点上安装 Apache 服务。此服务仅在活动节点上启动,并且将在活动节点发生故障时与 VIP 同时切换节点。
请参阅 Apache 章以获取详细的安装说明。
您必须在两个节点上安装 httpd
sudo dnf install -y httpd
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
警告
不要自己启动或激活服务。Pacemaker 会处理它。
默认情况下,将显示一个包含服务器名称的 HTML 页面
echo "<html><body>Node $(hostname -f)</body></html>" | sudo tee "/var/www/html/index.html"
Pacemaker 资源代理将使用 /server-status
页面(参见 Apache 章)来确定其运行状况。您必须通过在两个服务器上创建 /etc/httpd/conf.d/status.conf
文件来激活它
sudo vim /etc/httpd/conf.d/status.conf
<Location /server-status>
SetHandler server-status
Require local
</Location>
要创建资源,您将调用“WebSite”;您将调用 OCF 资源的 Apache 脚本,并在 heartbeat 命名空间中调用。
sudo pcs resource create WebSite ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf statusurl="http://localhost/server-status" op monitor interval=1min
集群将每分钟检查一次 Apache 的运行状况(op monitor interval=1min
)。
最后,要确保 Apache 服务与 VIP 地址在同一个节点上启动,您必须向集群添加一个约束
sudo pcs constraint colocation add WebSite with myclusterVIP INFINITY
也可以配置 Apache 服务,使其在 VIP 之后启动。如果 Apache 有监听 VIP 地址的 VHost 配置(Listen 192.168.1.12
),这将很有用
$ sudo pcs constraint order myclusterVIP then WebSite
Adding myclusterVIP WebSite (kind: Mandatory) (Options: first-action=start then-action=start)
测试故障转移¶
您将执行故障转移并测试您的 Web 服务器是否仍然可用
$ sudo pcs status
Cluster name: mycluster
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: server1 (version 2.1.7-5.el9_4-0f7f88312) - partition with quorum
...
Node List:
* Online: [ server1 server2 ]
Full List of Resources:
* myclusterVIP (ocf:heartbeat:IPaddr2): Started server1
* WebSite (ocf:heartbeat:apache): Started server1
您目前正在 server1 上工作。
$ curl http://192.168.1.12/
<html><body>Node server1</body></html>
模拟 server1 上的故障
sudo pcs node standby server1
$ curl http://192.168.1.12/
<html><body>Node server2</body></html>
如您所见,您的 Web 服务仍在运行,但现在它在 server2 上。
sudo pcs node unstandby server1
请注意,服务只中断了几秒钟,而 VIP 切换了,服务重新启动了。
集群故障排除¶
pcs status
命令¶
pcs status
命令提供有关集群总体状态的信息
$ sudo pcs status
Cluster name: mycluster
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: server1 (version 2.1.7-5.el9_4-0f7f88312) - partition with quorum
* Last updated: Tue Jul 9 12:25:42 2024 on server1
* Last change: Tue Jul 9 12:10:55 2024 by root via root on server1
* 2 nodes configured
* 2 resource instances configured
Node List:
* Online: [ server1 ]
* OFFLINE: [ server2 ]
Full List of Resources:
* myclusterVIP (ocf:heartbeat:IPaddr2): Started server1
* WebSite (ocf:heartbeat:apache): Started server1
Daemon Status:
corosync: active/enabled
pacemaker: active/enabled
pcsd: active/enabled
如您所见,两个服务器中的一台已离线。
pcs status corosync
命令¶
pcs status corosync
命令提供有关 corosync
节点状态的信息
$ sudo pcs status corosync
Membership information
----------------------
Nodeid Votes Name
1 1 server1 (local)
以及 server2 恢复后
$ sudo pcs status corosync
Membership information
----------------------
Nodeid Votes Name
1 1 server1 (local)
2 1 server2
crm_mon
命令¶
crm_mon
命令返回集群状态信息。使用 -1
选项可以显示一次集群状态并退出。
$ sudo crm_mon -1
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: server1 (version 2.1.7-5.el9_4-0f7f88312) - partition with quorum
* Last updated: Tue Jul 9 12:30:21 2024 on server1
* Last change: Tue Jul 9 12:10:55 2024 by root via root on server1
* 2 nodes configured
* 2 resource instances configured
Node List:
* Online: [ server1 server2 ]
Active Resources:
* myclusterVIP (ocf:heartbeat:IPaddr2): Started server1
* WebSite (ocf:heartbeat:apache): Started server1
corosync-*cfgtool*
命令¶
corosync-cfgtool
命令检查配置是否正确以及与集群的通信是否正常工作
$ sudo corosync-cfgtool -s
Local node ID 1, transport knet
LINK ID 0 udp
addr = 192.168.1.10
status:
nodeid: 1: localhost
nodeid: 2: connected
corosync-cmapctl
命令是用于访问对象数据库的工具。例如,您可以使用它来检查集群成员节点的状态
$ sudo corosync-cmapctl | grep members
runtime.members.1.config_version (u64) = 0
runtime.members.1.ip (str) = r(0) ip(192.168.1.10)
runtime.members.1.join_count (u32) = 1
runtime.members.1.status (str) = joined
runtime.members.2.config_version (u64) = 0
runtime.members.2.ip (str) = r(0) ip(192.168.1.11)
runtime.members.2.join_count (u32) = 2
runtime.members.2.status (str) = joined
研讨会¶
对于本研讨会,您需要两台服务器,它们都安装了 Pacemaker 服务,并已配置和保护,如前几章所述。
您将配置一个高可用的 Apache 集群。
您的两台服务器具有以下 IP 地址
- server1:192.168.1.10
- server2:192.168.1.11
如果您没有服务来解析名称,请用以下内容填充 /etc/hosts
文件
$ cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.1.10 server1 server1.rockylinux.lan
192.168.1.11 server2 server2.rockylinux.lan
您将使用 192.168.1.12
的 VIP 地址。
任务 1:安装和配置¶
要安装 Pacemaker,请启用 highavailability
存储库。
在两个节点上
sudo dnf config-manager --set-enabled highavailability
sudo dnf install pacemaker pcs
sudo firewall-cmd --permanent --add-service=high-availability
sudo firewall-cmd --reload
sudo systemctl enable pcsd --now
echo "pwdhacluster" | sudo passwd --stdin hacluster
在 server1 上
$ sudo pcs host auth server1 server2
Username: hacluster
Password:
server1: Authorized
server2: Authorized
$ sudo pcs cluster setup mycluster server1 server2
$ sudo pcs cluster start --all
$ sudo pcs cluster enable --all
$ sudo pcs property set stonith-enabled=false
任务 2:添加 VIP¶
您将在集群上创建的第一个资源是 VIP。
pcs resource create myclusterVIP ocf:heartbeat:IPaddr2 ip=192.168.1.12 cidr_netmask=24 op monitor interval=30s
检查集群状态
$ sudo pcs status
Cluster name: mycluster
Cluster Summary:
...
* 2 nodes configured
* 1 resource instance configured
Node List:
* Node server1: standby
* Online: [ server2 ]
Full List of Resources:
* myclusterVIP (ocf:heartbeat:IPaddr2): Started server2
任务 3:安装 Apache 服务器¶
在两个节点上执行此安装。
$ sudo dnf install -y httpd
$ sudo firewall-cmd --permanent --add-service=http
$ sudo firewall-cmd --reload
echo "<html><body>Node $(hostname -f)</body></html>" | sudo tee "/var/www/html/index.html"
sudo vim /etc/httpd/conf.d/status.conf
<Location /server-status>
SetHandler server-status
Require local
</Location>
任务 4:添加 httpd
资源¶
仅在 server1 上,使用所需的约束将新资源添加到集群。
sudo pcs resource create WebSite ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf statusurl="http://localhost/server-status" op monitor interval=1min
sudo pcs constraint colocation add WebSite with myclusterVIP INFINITY
sudo pcs constraint order myclusterVIP then WebSite
任务 5:测试您的集群¶
您将执行故障转移并测试您的 Web 服务器是否仍然可用
$ sudo pcs status
Cluster name: mycluster
Cluster Summary:
* Stack: corosync (Pacemaker is running)
* Current DC: server1 (version 2.1.7-5.el9_4-0f7f88312) - partition with quorum
...
Node List:
* Online: [ server1 server2 ]
Full List of Resources:
* myclusterVIP (ocf:heartbeat:IPaddr2): Started server1
* WebSite (ocf:heartbeat:apache): Started server1
您目前正在 server1 上工作。
$ curl http://192.168.1.12/
<html><body>Node server1</body></html>
模拟 server1 上的故障
sudo pcs node standby server1
$ curl http://192.168.1.12/
<html><body>Node server2</body></html>
如您所见,您的 Web 服务仍在运行,但现在运行在 server2 上。
sudo pcs node unstandby server1
请注意,在 VIP 切换和服务重新启动时,服务仅中断了几秒钟。
检验您的知识¶
pcs
命令是控制 pacemaker 集群的唯一命令吗?
哪个命令返回集群状态?
-
sudo pcs status
-
systemctl status pcs
-
sudo crm_mon -1
-
sudo pacemaker -t
作者:Antoine Le Morvan
贡献者:Steven Spencer,Ganna Zhyrnova