zoukankan      html  css  js  c++  java
  • ETCD:多机上的集群

    原文地址:cluster on multiple machines

    总览


    启动一个集群静态的要求是每一个集群中的成员需要知道其他成员的位置。在许多情况下,集群成员的IP可能无法提前知道。在这种情况下,etcd集群可以在发现服务的帮助下进行启动。
    一旦etcd集群已经启动,添加或移除成员可以通过运行时重新配置。在运行时重新配置之前,为了更好地理解设计,我们建议读运行时重新配置设计文档
    这篇引导etcd集群的启动将包括以下机制:

    • 静态
    • etcd发现
    • DNS发现

    每种引导机制都将用于创建具有以下详细信息的三台计算机etcd集群:

    Name Address Hostname
    infra0 10.0.1.10 infra0.example.com
    infra1 10.0.1.11 infra1.example.com
    infra2 10.0.1.12 infra2.example.com

    静态

    集群的成员,在启动之前它们的地址和集群的大小,我们可以通过设置initial-cluster参数使用离线的启动配置。每一个机器将会通过以下的环境变量或命令行获得配置信息:

    ETCD_INITIAL_CLUSTER="infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380"
    ETCD_INITIAL_CLUSTER_STATE=new
    
    --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 
    --initial-cluster-state new
    

    注意在initial-cluster中的URLs必须是已发布的对等的节点的URLs,即它们应该和对应的节点上的initial-advertise-peer-urls的值对应。
    如果为了测试的目的通过相同的配置分解多集群(或者创建和删除单个集群),值得注意的是每一个集群应该给予独一无二的initial-cluster-token,通过做这些工作,即使它们具有相同的配置,etcd也可以为集群成员生成独一无二的集群Id和成员ID。这样可以在可能会扰乱集群的跨集群中交互中保护etcd。
    etcd监听在listen-client-urls接受客户端流量,etcd将advertise-client-urls中指定的URLs告诉其他成员,代理,客户端。请确保潜在的客户端可以获取advertise-client-urls。一个常见的错误当远程的客户端应该访问etcd时设置advertise-client-urls为localhost或者将其保留为默认值。
    在每一台机器上,通过这些参数启动etcd:

    $ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 
      --listen-peer-urls http://10.0.1.10:2380 
      --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.10:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 
      --initial-cluster-state new
    
    $ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 
      --listen-peer-urls http://10.0.1.11:2380 
      --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.11:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 
      --initial-cluster-state new
    
    $ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 
      --listen-peer-urls http://10.0.1.12:2380 
      --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.12:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 
      --initial-cluster-state new
    

    initial-cluster开头的命令行参数将在etcd启动后被忽略。在初始化启动后可以自由删除环境变量或者命令行参数。如果配置信息在启动之后需要改变(例如在/从集群中添加或删除成员),看运行时配置引导。

    TLS

    etcd支持通过TLS协议进行加密通信。TLS通道可以在集群内部通信使用,也可以在节点和客户端流量通信时使用。这一部分列举了为节点和客户端TLS通信的集群设置。添加的etcd的TLS支持信息细节可以在安全引导中发现。

    自签名证书

    一个集群使用自签名证书加密流量和连接权限。使用自签名证书启动一个集群,每一个集群成员都需要含有一个独一无二的由共享的集群CA证书(ca.crt)签名的秘钥对(member.crt,member.key),用于节点连接和客户端连接。证书可以通过下面的etcdTLS设置例子中生成。
    对于每一台机器,etcd应该通过这些参数启动:

    $ etcd --name infra0 --initial-advertise-peer-urls https://10.0.1.10:2380 
      --listen-peer-urls https://10.0.1.10:2380 
      --listen-client-urls https://10.0.1.10:2379,https://127.0.0.1:2379 
      --advertise-client-urls https://10.0.1.10:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 
      --initial-cluster-state new 
      --client-cert-auth --trusted-ca-file=/path/to/ca-client.crt 
      --cert-file=/path/to/infra0-client.crt --key-file=/path/to/infra0-client.key 
      --peer-client-cert-auth --peer-trusted-ca-file=ca-peer.crt 
      --peer-cert-file=/path/to/infra0-peer.crt --peer-key-file=/path/to/infra0-peer.key
    
    $ etcd --name infra1 --initial-advertise-peer-urls https://10.0.1.11:2380 
      --listen-peer-urls https://10.0.1.11:2380 
      --listen-client-urls https://10.0.1.11:2379,https://127.0.0.1:2379 
      --advertise-client-urls https://10.0.1.11:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 
      --initial-cluster-state new 
      --client-cert-auth --trusted-ca-file=/path/to/ca-client.crt 
      --cert-file=/path/to/infra1-client.crt --key-file=/path/to/infra1-client.key 
      --peer-client-cert-auth --peer-trusted-ca-file=ca-peer.crt 
      --peer-cert-file=/path/to/infra1-peer.crt --peer-key-file=/path/to/infra1-peer.key
    
    $ etcd --name infra2 --initial-advertise-peer-urls https://10.0.1.12:2380 
      --listen-peer-urls https://10.0.1.12:2380 
      --listen-client-urls https://10.0.1.12:2379,https://127.0.0.1:2379 
      --advertise-client-urls https://10.0.1.12:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 
      --initial-cluster-state new 
      --client-cert-auth --trusted-ca-file=/path/to/ca-client.crt 
      --cert-file=/path/to/infra2-client.crt --key-file=/path/to/infra2-client.key 
      --peer-client-cert-auth --peer-trusted-ca-file=ca-peer.crt 
      --peer-cert-file=/path/to/infra2-peer.crt --peer-key-file=/path/to/infra2-peer.key
    

    自动化证书

    如果集群需要加密通信但是不需要连接时权限认证,etcd可以配置为自动生成秘钥.在初始化阶段,etcd成员基于他们的Ip地址和主机生成自己的秘钥.
    在每一台主机上,etcd需要根据这些参数进行启动:

    $ etcd --name infra0 --initial-advertise-peer-urls https://10.0.1.10:2380 
      --listen-peer-urls https://10.0.1.10:2380 
      --listen-client-urls https://10.0.1.10:2379,https://127.0.0.1:2379 
      --advertise-client-urls https://10.0.1.10:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 
      --initial-cluster-state new 
      --auto-tls 
      --peer-auto-tls
    
    $ etcd --name infra1 --initial-advertise-peer-urls https://10.0.1.11:2380 
      --listen-peer-urls https://10.0.1.11:2380 
      --listen-client-urls https://10.0.1.11:2379,https://127.0.0.1:2379 
      --advertise-client-urls https://10.0.1.11:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 
      --initial-cluster-state new 
      --auto-tls 
      --peer-auto-tls
    
    $ etcd --name infra2 --initial-advertise-peer-urls https://10.0.1.12:2380 
      --listen-peer-urls https://10.0.1.12:2380 
      --listen-client-urls https://10.0.1.12:2379,https://127.0.0.1:2379 
      --advertise-client-urls https://10.0.1.12:2379 
      --initial-cluster-token etcd-cluster-1 
      --initial-cluster infra0=https://10.0.1.10:2380,infra1=https://10.0.1.11:2380,infra2=https://10.0.1.12:2380 
      --initial-cluster-state new 
      --auto-tls 
      --peer-auto-tls
    

    错误案例

    在以下的例子中,新的主机没有包含在枚举的节点列表中,如果这是一个新的集群,节点需要被添加到初始化集群成员的列表中。

    $ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 
      --listen-peer-urls https://10.0.1.11:2380 
      --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.11:2379 
      --initial-cluster infra0=http://10.0.1.10:2380 
      --initial-cluster-state new
    etcd: infra1 not listed in the initial cluster config
    exit 1
    

    在以下的例子中,我们试图映射一个节点(infra0)到一个不同的地址(127.0.0.1:2380),而它在集群列表中的地址为(10.0.1.10:2380).如果这个节点监听多个端口,所有地址都必须要反射到initial-cluster参数配置中。

    $ etcd --name infra0 --initial-advertise-peer-urls http://127.0.0.1:2380 
      --listen-peer-urls http://10.0.1.10:2380 
      --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.10:2379 
      --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra2=http://10.0.1.12:2380 
      --initial-cluster-state=new
    etcd: error setting up initial cluster: infra0 has different advertised URLs in the cluster and advertised peer URLs list
    exit 1
    

    如果一个节点被配置成不同集群的参数并试图加入这个集群,etcd将会报出集群ID不匹配并退出.

    $ etcd --name infra3 --initial-advertise-peer-urls http://10.0.1.13:2380 
      --listen-peer-urls http://10.0.1.13:2380 
      --listen-client-urls http://10.0.1.13:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.13:2379 
      --initial-cluster infra0=http://10.0.1.10:2380,infra1=http://10.0.1.11:2380,infra3=http://10.0.1.13:2380 
      --initial-cluster-state=new
    etcd: conflicting cluster ID to the target cluster (c6ab534d07e8fcc4 != bc25ea2a74fb18b0). Exiting.
    exit 1
    

    发现服务

    在许多案例中,集群节点不能提前知道Ip地址。这在云服务提供商或者是使用DHCP的网络中很常见。在这种情况下,使用一个存在的etcd集群来启动一个新的节点而不是进行静态的配置,这个过程称为"节点发现".

    有两种方法可以用来发现节点:

    • etcd发现服务
    • DNS SRV 记录

    etcd 发现服务

    为了更好理解发现服务协议的设计,我们建议阅读发现服务协议文档.
    发现服务URL的生命周期
    一个发现URL标识一个独有的etcd集群而不是使用已有的发现URL。每一个etcd实例分享一个新的发现URL去启动新的集群。
    此外,发现URL应该只在初始化启动集群的时候使用,如果需要改变已经启动的集群中的成员关系,看运行时重新配置引导.
    自定义etcd发现服务
    发现服务用于启动一个存在的集群,如果使用一个私有的etcd集群,像这样创建URL:

    $ curl -X PUT https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83/_config/size -d value=3
    

    通过设置URL中Key的大小,创建发现URL的集群预期大小为3.
    在这种情况下URL将会这样使用:

    https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
    

    当etcd成员启动时将使用

    https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
    

    文件夹进行注册。
    每一个成员必须含有一个不同的命名参数。Hostname或者machine-id将是一个好的选择。发现服务的失败通常由于重复的名字。
    现在我们通过这些参数启动etcd的每一个成员:

    $ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 
      --listen-peer-urls http://10.0.1.10:2380 
      --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.10:2379 
      --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
    
    $ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 
      --listen-peer-urls http://10.0.1.11:2380 
      --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.11:2379 
      --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
    
    $ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 
      --listen-peer-urls http://10.0.1.12:2380 
      --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.12:2379 
      --discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
    

    一旦所有主机被注册后,每一个集群成员将会集群启动之前在自定义发现服务中注册自己。
    公共的etcd发现服务
    如果没有可以获得的集群,使用托管在discovery.etcd.io的公共发现服务。通过"new"主机,创建一个私有的发现URL,使用以下命令:

    $ curl https://discovery.etcd.io/new?size=3
    https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    

    将会创建一个初始化成员数量为3的集群,如果没有设置大小,将默认为3.

    ETCD_DISCOVERY=https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    
    --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    

    现在我们通过这些相关的参数启动每一个etcd成员:

    $ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 
      --listen-peer-urls http://10.0.1.10:2380 
      --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.10:2379 
      --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    
    $ etcd --name infra1 --initial-advertise-peer-urls http://10.0.1.11:2380 
      --listen-peer-urls http://10.0.1.11:2380 
      --listen-client-urls http://10.0.1.11:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.11:2379 
      --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    
    $ etcd --name infra2 --initial-advertise-peer-urls http://10.0.1.12:2380 
      --listen-peer-urls http://10.0.1.12:2380 
      --listen-client-urls http://10.0.1.12:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.12:2379 
      --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    

    一旦所有主机被注册后,每一个集群成员将会集群启动之前在自定义发现服务中注册自己。
    etcd使用环境变量ETCD_DISCOVERY_PROXY通过HTTP代理连接发现服务。
    错误和警告案例
    发现服务错误:

    $ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 
      --listen-peer-urls http://10.0.1.10:2380 
      --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.10:2379 
      --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    etcd: error: the cluster doesn’t have a size configuration value in https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de/_config
    exit 1
    

    警告
    这里有一个严重的警告表明发现服务URL将被这台主机忽略。

    $ etcd --name infra0 --initial-advertise-peer-urls http://10.0.1.10:2380 
      --listen-peer-urls http://10.0.1.10:2380 
      --listen-client-urls http://10.0.1.10:2379,http://127.0.0.1:2379 
      --advertise-client-urls http://10.0.1.10:2379 
      --discovery https://discovery.etcd.io/3e86b59982e49066c5d813af1c2e2579cbf573de
    etcdserver: discovery token ignored since a cluster has already been initialized. Valid log found at /var/lib/etcd
    

    DNS 发现服务

    DNS SRV 记录可以用来作为发现机制。--discovery-srv参数可用于设置可以找到发现SRV记录的DNS域名。 设置--discovery-srv example.com会导致DNS SRV记录按照列出的顺序进行查找:

    • _etcd-server-ssl._tcp.example.com
    • _etcd-server._tcp.example.com

    如果找到_etcd-server-ssl._tcp.example.com,则etcd将尝试通过TLS进行引导过程。
    为了帮助客户端发现etcd集群,按照列出的顺序查找以下DNS SRV记录:

    • _etcd-client._tcp.example.com
    • _etcd-client-ssl._tcp.example.com

    如果找到了_etcd-client-ssl._tcp.example.com,则客户端将尝试通过SSL/TLS与etcd集群进行通信。
    如果etcd使用TLS,则发现SRV记录(例如example.com)必须与主机名一起包含在SSL证书DNS SAN中,否则集群将失败,并显示以下日志消息:

    [...] rejected connection from "10.0.1.11:53162" (error "remote error: tls: bad certificate", ServerName "example.com")
    

    如果etcd使用的是没有自定义证书颁发机构的TLS,则发现域(例如example.com)必须与SRV记录域(例如infra1.example.com)匹配。 这是为了缓解伪造SRV记录指向不同域的攻击。 该域将在PKI下拥有有效的证书,但由未知的第三方控制。
    -discovery-srv-name参数还为在发现期间查询的SRV名称配置了后缀。 使用此参数可以区分同一域下的多个etcd集群。 例如,如果设置了Discovery-srv = example.com-discovery-srv-name = foo,则会进行以下DNS SRV查询:

    • _etcd-server-ssl-foo._tcp.example.com
    • _etcd-server-foo._tcp.example.com

    创建DNS SRV记录

    $ dig +noall +answer SRV _etcd-server._tcp.example.com
    _etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra0.example.com.
    _etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra1.example.com.
    _etcd-server._tcp.example.com. 300 IN  SRV  0 0 2380 infra2.example.com.
    
    $ dig +noall +answer SRV _etcd-client._tcp.example.com
    _etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra0.example.com.
    _etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra1.example.com.
    _etcd-client._tcp.example.com. 300 IN SRV 0 0 2379 infra2.example.com.
    
    $ dig +noall +answer infra0.example.com infra1.example.com infra2.example.com
    infra0.example.com.  300  IN  A  10.0.1.10
    infra1.example.com.  300  IN  A  10.0.1.11
    infra2.example.com.  300  IN  A  10.0.1.12
    

    使用DNS引导etcd集群
    etcd群集成员可以公告域名或IP地址,引导过程将解析DNS A记录。 从3.2开始(3.1将显示警告),--listen-peer-urls--listen-client-urls将拒绝网络接口绑定的域名。
    --initial-advertise-peer-urls中的解析地址必须与SRV目标中的解析地址之一匹配。 etcd成员读取解析的地址,以查找其是否属于SRV记录中定义的集群。

    $ etcd --name infra0 
    --discovery-srv example.com 
    --initial-advertise-peer-urls http://infra0.example.com:2380 
    --initial-cluster-token etcd-cluster-1 
    --initial-cluster-state new 
    --advertise-client-urls http://infra0.example.com:2379 
    --listen-client-urls http://0.0.0.0:2379 
    --listen-peer-urls http://0.0.0.0:2380
    
    $ etcd --name infra1 
    --discovery-srv example.com 
    --initial-advertise-peer-urls http://infra1.example.com:2380 
    --initial-cluster-token etcd-cluster-1 
    --initial-cluster-state new 
    --advertise-client-urls http://infra1.example.com:2379 
    --listen-client-urls http://0.0.0.0:2379 
    --listen-peer-urls http://0.0.0.0:2380
    
    $ etcd --name infra2 
    --discovery-srv example.com 
    --initial-advertise-peer-urls http://infra2.example.com:2380 
    --initial-cluster-token etcd-cluster-1 
    --initial-cluster-state new 
    --advertise-client-urls http://infra2.example.com:2379 
    --listen-client-urls http://0.0.0.0:2379 
    --listen-peer-urls http://0.0.0.0:2380
    

    集群还可以使用IP地址而不是域名进行引导:

    $ etcd --name infra0 
    --discovery-srv example.com 
    --initial-advertise-peer-urls http://10.0.1.10:2380 
    --initial-cluster-token etcd-cluster-1 
    --initial-cluster-state new 
    --advertise-client-urls http://10.0.1.10:2379 
    --listen-client-urls http://10.0.1.10:2379 
    --listen-peer-urls http://10.0.1.10:2380
    
    $ etcd --name infra1 
    --discovery-srv example.com 
    --initial-advertise-peer-urls http://10.0.1.11:2380 
    --initial-cluster-token etcd-cluster-1 
    --initial-cluster-state new 
    --advertise-client-urls http://10.0.1.11:2379 
    --listen-client-urls http://10.0.1.11:2379 
    --listen-peer-urls http://10.0.1.11:2380
    
    $ etcd --name infra2 
    --discovery-srv example.com 
    --initial-advertise-peer-urls http://10.0.1.12:2380 
    --initial-cluster-token etcd-cluster-1 
    --initial-cluster-state new 
    --advertise-client-urls http://10.0.1.12:2379 
    --listen-client-urls http://10.0.1.12:2379 
    --listen-peer-urls http://10.0.1.12:2380
    

    自从v3.1.0(v3.2.9除外),因此在etcd --discovery-srv = example.com中配置了TLS时,服务器仅在提供的证书具有根域example.com作为Subject Alternative(SAN)字段中的条目时,对等方/客户端进行身份验证。请参阅DNS SRV的注释
    网关
    etcd网关是一个简单的TCP代理,可将网络数据转发到etcd集群。 请阅读网关指南以获取更多信息。
    代理
    设置--proxy参数时,etcd以代理模式运行。 此代理模式仅支持etcd v2 API; 目前尚无计划支持v3 API。 相反,为了支持v3 API,etcd 3.0版本之后将提供具有增强功能的新代理。
    要使用v2 API代理设置etcd集群,请阅读etcd 2.3版本中的集群文档

  • 相关阅读:
    python中元类(metaclass)的理解
    aiohttp
    async/await
    asyncio
    协程
    Bayesian Non-Exhaustive Classification A case study:online name disambiguation using temporal record streams
    技术网址
    网站
    各种网址
    OpenGL学习网址2
  • 原文地址:https://www.cnblogs.com/cbkj-xd/p/11934613.html
Copyright © 2011-2022 走看看