热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Istio入门(dignity)

1.简介在本教程中,我们将介绍服务网格的基础知识,并了解它如何实现分布式系统架构。我们将主要关注Istio,它是服务网格的一种具体实现。在此过程中,我们将介绍Istio的核心架构。

1. 简介

在本教程中,我们将介绍服务网格的基础知识,并了解它如何实现分布式系统架构。

我们将主要关注Istio,它是服务网格的一种具体实现。在此过程中,我们将介绍Istio的核心架构。

2. 什么是服务网格?

在过去的几十年中,我们已经看到了单体应用程序开始拆分为较小的应用程序。此外,诸如Docker之类的容器化技术和诸如Kubernetes之类的编排系统加速了这一变化。

尽管在像Kubernetes这样的分布式系统上采用微服务架构有许多优势,但它也具有相当的复杂性。由于分布式服务必须相互通信,因此我们必须考虑发现,路由,重试和故障转移。

还有其他一些问题,例如安全性和可观察性,我们还必须注意以下问题:

Istio入门(dignity)

现在,在每个服务中建立这些通信功能可能非常繁琐,尤其是当服务范围扩大且通信变得复杂时,更是如此。这正是服务网格可以为我们提供帮助的地方。基本上,服务网格消除了在分布式软件系统中管理所有服务到服务通信的责任。

服务网格能够通过一组网络代理来做到这一点。本质上,服务之间的请求是通过与服务一起运行但位于基础结构层之外的代理路由的:

Istio入门(dignity)

这些代理基本上为服务创建了一个网状网络–因此,名称为服务网格!通过这些代理,服务网格能够控制服务到服务通信的各个方面。这样,我们可以使用它来解决分布式计算的八个谬误,这是一组断言,描述了我们经常对分布式应用程序做出的错误假设。

3. 服务网格的特征

现在,让我们了解服务网格可以为我们提供的一些功能。请注意,实际功能列表取决于服务网格的实现。但是,总的来说,我们应该在所有实现中都期望其中大多数功能。

我们可以将这些功能大致分为三类:流量管理安全性可观察性

3.1. 流量管理

服务网格的基本特征之一是流量管理。这包括动态服务发现和路由。尤其影子流量和流量拆分功能,这些对于实现金丝雀发布和A/B测试非常有用。

由于所有服务之间的通信都是由服务网格处理的,因此它还启用了一些可靠性功能。例如,服务网格可以提供重试,超时,速率限制和断路器。这些现成的故障恢复功能使通信更加可靠。

3.2. 安全性

服务网格通常还处理服务到服务通信的安全性方面。这包括通过双向TLS(mTLS)强制进行流量加密,通过证书验证提供身份验证以及通过访问策略确保授权。

服务网格中还可能存在一些有趣的安全用例。例如,我们可以实现网络分段,从而允许某些服务进行通信而禁止其他服务。而且,服务网格可以为审核需求提供精确的历史信息。

3.3. 可观察性

强大的可观察性是处理分布式系统复杂性的基本要求。由于服务网格可以处理所有通信,因此正确放置了它可以提供可观察性的功能。例如,它可以提供有关分布式追踪的信息。

服务网格可以生成许多指标,例如延迟,流量,错误和饱和度。此外,服务网格还可以生成访问日志,为每个请求提供完整记录。这些对于理解单个服务以及整个系统的行为非常有用。

4. Istio简介

Istio是最初由IBM,Google和Lyft开发的服务网格的开源实现。它可以透明地分层到分布式应用程序上,并提供服务网格的所有优点,例如流量管理,安全性和可观察性。

它旨在与各种部署配合使用,例如本地部署,云托管,Kubernetes容器以及虚拟机上运行的服务程序。尽管Istio与平台无关,但它经常与Kubernetes平台上部署的微服务一起使用。

从根本上讲,Istio的工作原理是以Sidcar的形式将Envoy的扩展版本作为代理布署到每个微服务中:

Istio入门(dignity)

该代理网络构成了Istio架构的数据平面。这些代理的配置和管理是从控制平面完成的:

Istio入门(dignity)

控制平面基本上是服务网格的大脑。它为数据平面中的Envoy代理提供发现,配置和证书管理。

当然,只有在拥有大量相互通信的微服务时,我们才能体现Istio的优势。在这里,sidecar代理在专用的基础架构层中形成一个复杂的服务网格:

Istio入门(dignity)

Istio在与外部库和平台集成方面非常灵活。例如,我们可以将Istio与外部日志记录平台,遥测或策略系统集成。

5. 了解Istio组件

我们已经看到,Istio体系结构由数据平面和控制平面组成。此外,还有几个使Istio起作用的核心组件。

在本节中,我们将详细介绍这些核心组件。

5.1. 数据平面

Istio的数据平面主要包括Envoy代理的扩展版本。 Envoy是一个开源边缘和服务代理,可帮助将网络问题与底层应用程序分离开来。应用程序仅向localhost发送消息或从localhost接收消息,而无需了解网络拓扑。

Envoy的核心是在OSI模型的L3和L4层运行的网络代理。它通过使用可插入网络过滤器链来执行连接处理。此外,Envoy支持用于基于HTTP的流量的附加L7层过滤器。而且,Envoy对HTTP/2和gRPC传输具有一流的支持。

Istio作为服务网格提供的许多功能实际上是由Envoy代理的基础内置功能启用的:


  • 流量控制:Envoy通过HTTP,gRPC,WebSocket和TCP流量的丰富路由规则启用细粒度的流量控制应用

  • 网络弹性:Envoy包括对自动重试,断路和故障注入的开箱即用支持

  • 安全性:Envoy还可以实施安全策略,并对基础服务之间的通信应用访问控制和速率限制

Envoy在Istio上表现出色的另一个原因之一是它的可扩展性。 Envoy提供了基于WebAssembly的可插拔扩展模型。这在定制策略执行和遥测生成中非常有用。此外,我们还可以使用基于Proxy-Wasm沙箱API的Istio扩展在Istio中扩展Envoy代理。

5.2. 控制面

如上所述,控制平面负责管理和配置数据平面中的Envoy代理。在Istio架构中,控制面核心组件是istiod,Istiod负责将高级路由规则和流量控制行为转换为特定于Envoy的配置,并在运行时将其传播到Sidercar。

如果我们回顾一下Istio控制平面的架构,将会注意到它曾经是一组相互协作的独立组件。它包括诸如用于服务发现的Pilot,用于配置的Galley,用于证书生成的Citadel以及用于可扩展性的Mixer之类的组件。由于复杂性,这些单独的组件被合并为一个称为istiod的单个组件。

从根本上来说,istiod仍使用与先前各个组件相同的代码和API。例如,Pilot负责抽象特定于平台的服务发现机制,并将其合成为Sidecar可以使用的标准格式。因此,Istio可以支持针对多个环境(例如Kubernetes或虚拟机)的发现。

此外,istiod还提供安全性,通过内置的身份和凭据管理实现强大的服务到服务和最终用户身份验证。此外,借助istiod,我们可以基于服务身份来实施安全策略。该过程也充当证书颁发机构(CA)并生成证书,以促进数据平面中的相互TLS(MTLS)通信。

6. Istio 工作原理

我们已经了解了服务网格的典型特征是什么。此外,我们介绍了Istio架构及其核心组件的基础。现在,是时候了解Istio如何通过其架构中的核心组件提供这些功能了。

我们将专注于我们之前经历过的相同类别的功能。

6.1. 流量管理

我们可以使用Istio流量管理API对服务网格中的流量进行精细控制。我们可以使用这些API将自己的流量配置添加到Istio。此外,我们可以使用Kubernetes自定义资源定义(CRD)定义API资源。帮助我们控制流量路由的关键API资源是虚拟服务和目标规则:

Istio入门(dignity)

基本上,虚拟服务使我们可以配置如何将请求路由到Istio服务网格中的服务。因此,虚拟服务由一个或多个按顺序评估的路由规则组成。评估虚拟服务的路由规则后,将应用目标规则。目标规则有助于我们控制到达目标的流量,例如,按版本对服务实例进行分组。

6.2. 安全性

Istio为每个服务提供身份。与每个Envoy代理一起运行的Istio代理与istiod一起使用以自动进行密钥和证书轮换:

Istio入门(dignity)

Istio提供两种身份验证–对等身份验证和请求身份验证。对等身份验证用于服务到服务的身份验证,其中Istio提供双向TLS作为全栈解决方案。请求身份验证用于最终用户身份验证,其中Istio使用自定义身份验证提供程序或OpenID Connect(OIDC)提供程序提供JSON Web令牌(JWT)验证。

Istio还允许我们通过简单地将授权策略应用于服务来实施对服务的访问控制。授权策略对Envoy代理中的入站流量实施访问控制。这样,我们就可以在各种级别上应用访问控制:网格,命名空间和服务范围。

6.3. 可观察性

Istio为网格网络内的所有服务通信生成详细的遥测,例如度量,分布式跟踪和访问日志。 Istio生成一组丰富的代理级指标,面向服务的指标和控制平面指标。

之前,Istio遥测体系结构将Mixer作为核心组件。但是从Telemetry v2开始,混音器提供的功能已替换为Envoy代理插件:

Istio入门(dignity)

此外,Istio通过Envoy代理生成分布式跟踪。 Istio支持许多跟踪后端,例如Zipkin,Jaeger,Lightstep和Datadog。我们还可以控制跟踪速率的采样率。此外,Istio还以一组可配置的格式生成服务流量的访问日志。

7. Istio实战

上面我们已经讲述了Istio原理和架构,接下来我们开始实战部分。首先,我们将在Kubernetes集群中安装Istio。此外,我们将使用一个简单的基于微服务的应用程序来演示Istio在Kubernetes上的功能。

7.1. 安装

有多种安装Istio的方法,但最简单的方法是下载并解压缩特定操作系统(例如Windows)的最新版本。提取的软件包在bin目录中包含istioctl客户端二进制文件。我们可以使用istioctl在目标Kubernetes集群上安装Istio:

istioctl install --set profile=demo -y

这会使用演示配置文件将Istio组件安装在默认的Kubernetes集群上。我们还可以使用任何其他特定于供应商的配置文件来代替演示。

最后,当我们在此Kubernetes集群上部署任何应用程序时,我们需要指示Istio自动注入Envoy sidecar代理:

kubectl label namespace default istio-injection=enabled

我们在这里使用kubectl的前提是,我们的机器上已经有像Minikube这样的Kubernetes集群和Kubernetes CLI kubectl。

7.2. 示例应用

为了演示,我们将想象一个非常简单的在线下订单应用程序。该应用程序包含三个微服务,它们相互交互以满足最终用户的订购请求:

Istio入门(dignity)

我们没有讨论这些微服务的细节,但是使用Spring Boot和REST API可以很简单地创建它们。最重要的是,我们为这些微服务创建了一个Docker镜像,以便我们可以将它们部署在Kubernetes上。

7.3. 部署

在像Minikube这样的Kubernetes集群上部署容器化的工作负载非常简单。我们将使用Deployment和Service资源类型来声明和访问工作负载。通常,我们在YAML文件中定义它们:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: order-service
namespace: default
spec:
replicas: 1
template:
metadata:
labels:
app: order-service
version: v1
spec:
containers:
- name: order-service
image: kchandrakant/order-service:v1
resources:
requests:
cpu: 0.1
memory: 200
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: order-service

对于订单服务的“部署和服务”,这是一个非常简单的定义。同样,我们可以为库存服务和运输服务定义YAML文件。

使用kubectl部署这些资源也非常简单:

kubectl apply -f booking-service.yaml -f inventory-service.yaml -f shipping-service.yaml

由于我们已经为默认命名空间启用了自动注入Envoy sidecar代理,因此一切都会由istiod来处理。或者,我们可以使用istioctl的kube-inject命令手动注入Envoy sidecar代理。

7.4. 访问应用

现在,Istio主要负责处理所有的网状网络流量。因此,默认情况下,不允许进出网格的任何流量。 Istio使用网关来管理来自网格的入站和出站流量。这样,我们可以精确地控制进入或离开网格的流量。 Istio提供了一些预配置的网关代理部署:istio-ingressgateway和istio-egressgateway。

我们将为我们的应用程序创建一个网关和一个虚拟服务来实现此目的:

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: booking-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: booking
spec:
hosts:
- "*"
gateways:
- booking-gateway
http:
- match:
- uri:
prefix: /api/v1/booking
route:
- destination:
host: booking-service
port:
number: 8080

在这里,我们利用了Istio提供的默认入口控制器。此外,我们已经定义了一个虚拟服务,将我们的请求路由到预订服务。

同样,我们也可以为来自网格的出站流量定义出口网关。

8. Istio的常见用例

现在,我们已经看到了如何使用Istio在Kubernetes上部署一个简单的应用程序。但是,我们仍然没有利用Istio为我们启用的任何有趣功能。在本节中,我们将介绍服务网格的一些常见用例,并了解如何使用Istio为我们的简单应用程序实现它们。

8.1. 请求路由

我们可能要以特定方式处理请求路由的原因有多个。例如,我们可能会部署微服务的多个版本,例如运输服务,并希望仅将一小部分请求路由到新版本。

我们可以使用虚拟服务的路由规则来实现这一点:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: shipping-service
spec:
hosts:
- shipping-service
http:
- route:
- destination:
host: shipping-service
subset: v1
weight: 90
- destination:
host: shipping-service
subset: v2
weight: 10
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: shipping-service
spec:
host: shipping-service
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2

路由规则还允许我们基于诸如header参数之类的属性来定义匹配条件。此外,目的地字段指定与条件匹配的流量的实际目的地。

8.2. 熔断

熔断器基本上是一种软件设计模式,用于检测故障并封装防止故障进一步级联的逻辑。这有助于创建有弹性的微服务应用程序,以限制故障和延迟尖峰的影响。

在Istio中,我们可以使用DestinationRule中的trafficPolicy配置在调用诸如清单服务之类的服务时应用熔断:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: inventory-service
spec:
host: inventory-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100

在这里,我们将DestinationRule配置为maxConnections为1,httpMaxPendingRequests为1,maxRequestsPerConnection为1。这实际上意味着,如果我们将并发请求数超过1,熔断器将开始trap一些请求。

8.3. 启用双向 TLS

双向身份验证是指双方在诸如TLS之类的身份验证协议中同时相互进行身份验证的情况。默认情况下,具有代理的服务之间的所有流量在Istio中都使用相互TLS。但是,没有代理的服务仍继续以纯文本格式接收流量。

虽然Istio将具有代理的服务之间的所有流量自动升级为双向TLS,但这些服务仍可以接收纯文本流量。我们可以选择使用PeerAuthentication策略在整个网格范围内实施双向TLS:

apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
namespace: "istio-system"
spec:
mtls:
mode: STRICT

我们还提供了对每个命名空间或服务而不是在网格范围内强制实施双向TLS的选项。但是,特定于服务的PeerAuthentication策略优先于命名空间范围的策略。

8.4. 使用JWT进行访问控制

JSON Web令牌(JWT)是用于创建数据的标准,该数据的有效载荷中包含声明许多声明的JSON。为了在身份提供者和服务提供者之间传递经过身份验证的用户的身份和标准或自定义声明,这一点已被广泛接受。

我们可以在Istio中启用授权策略,以允许访问基于JWT的预订服务之类的服务:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: default
spec:
selector:
matchLabels:
app: booking-service
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["testing@baeldung.com/testing@baeldung.io"]

在这里,AuthorizationPolicy强制所有请求具有有效的JWT,并将requestPrincipal设置为特定值。 Istio通过组合声明JWT的iss和sub来创建requestPrincipal属性。

9. 思考

因此,到目前为止,我们已经看到像Istio这样的服务网格如何使我们更轻松地处理诸如微服务之类的分布式架构中的许多常见问题。但是尽管如此,Istio还是一个复杂的系统,会增加最终部署的复杂性。与其他所有技术一样,Istio并非灵丹妙药,必须谨慎使用。

9.1. 我们应该始终使用服务网格吗?

尽管我们已经看到了使用服务网格的足够理由,但下面列举了一些可能促使我们不使用它的原因:


  • 服务网格处理所有服务到服务的通信,而部署和操作服务网格则需要支付额外的费用。对于较简单的应用程序,这可能是不合理的

  • 由于我们已经习惯于处理一些此类问题,例如应用程序代码中的熔断,因此可能导致服务网格中的重复处理

  • 越来越依赖于诸如服务网格之类的外部系统可能会损害应用程序的可移植性,尤其是因为没有针对服务网格的行业标准

  • 由于服务网格通常通过拦截通过代理的网格流量来工作,因此它可能会给请求增加不希望的延迟

  • 服务网格增加了许多其他组件和配置,需要精确处理。这需要专业知识,并增加了学习曲线
    最后,我们可能最终将操作逻辑(应在服务网格中存在)与业务逻辑(不应在服务网格中)混合在一起

因此,正如我们所看到的,服务网格的故事不仅仅涉及好处,但这并不意味着它们不是真的。对我们来说,重要的是要仔细评估我们的需求和应用程序的复杂性,然后权衡服务网格的好处和它们所增加的复杂性。

9.2. Istio的替代品有哪些?

尽管Istio非常受欢迎,并得到了业内一些领导者的支持,但它当然不是唯一的选择。尽管我们在这里无法进行全面的比较,但让我们看一下Linkerd和Consul这两个选项。

Linkerd是已为Kubernetes平台创建的开源服务网格。它也很受欢迎,目前在CNCF中具有孵化项目的地位。它的工作原理类似于Istio等任何其他服务网格。它还利用TCP代理来处理网格流量。 Linkerd使用用Rust编写的微型代理,称为Linkerd代理。

总体而言,Linkerd并不比Istio复杂,因为它仅支持Kubernetes。但是,除此之外,Linkerd中可用的功能列表与Istio中可用的功能非常相似。 Linkerd的核心架构也非常类似于Istio。基本上,Linkerd包含三个主要组件:用户界面,数据平面和控制平面。

Consul是HashiCorp的服务网格的开源实现。它的好处是可以与HashiCorp的其他基础架构管理产品套件很好地集成,以提供更广泛的功能。 Consul中的数据平面可以灵活地支持代理以及本机集成模型。它带有内置代理,但也可以与Envoy一起使用。

除了Kubernetes,Consul还可以与Nomad等其他平台一起使用。 Consul通过在每个节点上运行Consul代理以执行运行状况检查来工作。这些代理与一台或多台存储和复制数据的Consul服务器通信。尽管它提供了服务网格(如Istio)的所有标准功能,但它是部署和管理的更复杂的系统。

10. 总结

总而言之,在本教程中,我们介绍了服务网格模式的基本概念以及它提供给我们的功能。特别是,我们详细介绍了Istio。这涵盖了Istio的核心架构及其基本组件。此外,我们详细介绍了一些常见用例的安装和使用Istio的细节。、


推荐阅读
  • Istio是一个用来连接、管理和保护微服务的开放平台。Istio提供一种简单的方式来为已部署的服务建 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 微服务之总体架构篇
    一、单体架构存在的问题缺点:1、难以维护:当单体应用业务不断迭代后代码量非常臃肿,模整个项目非常复杂,每次更改代码都可能带来新的bug;2、部署项目麻烦:庞大之后项目部署效率 ... [详细]
  • golang反射,golang反射性能
    本文目录一览:1、关于反射2、尝试用golan ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 推荐系统遇上深度学习(十七)详解推荐系统中的常用评测指标
    原创:石晓文小小挖掘机2018-06-18笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值, ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • mysqldinitializeconsole失败_mysql03误删除了所有用户解决办法
    误删除了所有用户解决办法第一种方法(企业常用)1.将数据库down掉[rootdb03mysql]#etcinit.dmysqldstopShuttingdownMySQL..SU ... [详细]
  • [翻译]微服务设计模式5. 服务发现服务端服务发现
    服务之间需要互相调用,在单体架构中,服务之间的互相调用直接通过编程语言层面的方法调用就搞定了。在传统的分布式应用的部署中,服务地 ... [详细]
  • 前面的已经介绍过如何用webpack打包一个项目,细心的人会发现,打包后想要项目运行起来必须要把打包后的项目上传到服务器中,或者静态的项目 ... [详细]
author-avatar
国王的驴耳朵要吐槽
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有