UDN-企业互联网技术人气社区

板块导航

浏览  : 2665
回复  : 0

[技术交流] Docker网络和服务发现(下)

[复制链接]
genie1003的头像 楼主
发表于 2016-7-12 10:45:25 | 显示全部楼层 |阅读模式
  技术

  该小节介绍了各种技术和它们的优缺点,并提供了网上的更多资源(如果你想获得这些技术的实践经验,你可以看看Adrian Mouat的书Using Docker)。

  ZooKeeper

  Apache ZooKeeper是ASF的顶级项目,基于JVM的集中式配置管理工具,提供了与Google的Chubby相兼容的功能。ZooKeeper(ZK)将数据载荷组织成文件系统,成为znodes的层级结构。在集群中,选举出一个leader节点,客户端能够连接到服务器中的任意一个来获取数据。一个ZK集群中需要2n+1个节点。最常见的配置是3、5、7个节点。

  ZooKeeper是经战场证明的、成熟的、可扩展的解决方案,但是也有一下缺点。有些人认为ZK集群的安装和管理不是一个令人愉快的体验。我碰到的大多数ZK问题都是因为某个服务(我想到了Apache Storm)错误地使用了它。它们可能在znodes里放入了太多数据,更糟糕的是,他们的读写率很不健康(unhealthy read-write ratio),特别是写得太快。如果你打算使用ZK的话,至少考虑使用高层接口。例如,Apache Curator封装了ZK,提供了大量的方法;Netflix的Exhibitor可以管理和监控一个ZK集群。

  从图4-1可以看出,你可以看到两个组件:R/W(作为注册监控器(registration watcher),你需要自己提供)和NGINX(被R/W控制)。当一个容器被调度到一个节点上时,它会在ZK中注册,使用一个路径为/$nodeID/$containerID的znode,IP地址作为数据载荷。R/W监控znodes节点的变化,然后相应地配置NGINX。这种方法对于HAProxy和其他负载均衡器也同样有效。
2.png

  图 4-1

  etcd

  etcd是由CoreOS团队使用Go语言编写的。它是一个轻量级的、分布式键值对数据库,使用Raft算法实现一致性(带有leader选举的leader-follower模型),在集群中使用复制日志(replicated log)向followers分发lead收到的写请求。从某种意义上说,在概念上,etcd和ZK是相当类似的。虽然负载数据是任意的,但是etcd的HTTP API是基于JSON的。就像ZK一样,你可以观察到etcd保存的值的变化。etcd的一个非常有用的特性是键的TTL,是服务发现的重要的一个结构单元。和ZK一样,一个etcd集群至少需要2n+1个节点。

  etcd的安全模型支持基于TLS/SSL和客户端证书加密的线上加密(on-the-wire encryption),加密可以发生在客户端和集群之间,也可以在etcd节点之间。

  在图4-2中,你可以看到etcd服务发现的搭建和ZK是相当类似的。主要区别在于etcd使用confd来配置NGINX,而不是使用自己编写的脚本。和ZK一样,这种搭建方法也适用于HAProxy或者其他负载均衡器。
2.png

  图 4-2

  Consul

  Consul是HashiCorp的产品,也是用Go语言写的,功能有服务注册、服务发现和健康检查,可以使用HTTP API或者DNS来查询服务。Consul支持多数据中心的部署。

  Consul的其中一个特性是一个分布式键值对数据库,与etcd相似。它也是用Raft一致性算法(同样需要2n+1个节点),但是部署方式是不同的。Consul有agent的概念,有两种运行方式:服务器模式(提供键值对数据库和DNS)和客户端模式(注册服务和健康检查),使用serf实现了成员和节点发现。

  使用Consul,你有四种方式来实现服务发现(从最可取到最不可取):

  • 使用服务定义配置文件(service definition config file),由Consul agent解释。
  • 使用traefik等工具,其中有Consul后端(backend)。
  • 编写你自己的进程通过HTTP API注册服务。
  • 让服务自己使用HTTP API来注册服务。

  想要学习Consul来做服务发现吗?请阅读这两篇文章:Consul Service Discovery with Docker和Docker DNS & Service Discovery with Consul and Registrator。

  纯基于DNS的解决方案

  在互联网中,DNS经受了数十年的战场验证,是很健壮的。DNS系统的最终一致性、特定的客户端强制性地(aggressively)缓存DNS查询结果、对于SRV记录的依赖这些因素使你明白这是正确的选择。

  本小节之所以叫做“纯基于DNS的解决方案”的原因是,技术上讲Consul也是用了DNS服务器,但这只是Consul做服务发现的其中一个选项。以下是著名的、常用的、纯粹的基于DNS的服务发现解决方案:

  Mesos-DNS

  该解决方案是专门用于Apache Mesos的服务发现的。使用Go编写,Mesos-DNS下拉任意任务的有效的Mesos Master进程,并通过DNS或HTTP API暴露IP:PORT信息。对于其他主机名或服务的DNS请求,Mesos-DNS可以使用一个外部的域名服务器或者你的现有DNS服务器来转发Mesos任务的请求到Mesos-DNS。

  SkyDNS

  使用etcd,你可以将你的服务通告给SkyDNS,SkyDNS在etcd中保存了服务定义,并更新DNS记录。你的客户端应用发送DNS请求来发现服务。因此,在功能层面,SkyDNS与Consul是相当类似的,只是没有健康检查。

  WeaveDNS

  WeaveDNS由Weave 0.9引入,作为Weave网络的服务发现解决方案,允许容器按照主机名找到其他容器的IP地址。在Weave 1.1中,引入了所谓的Gossip DNS,通过缓存和超时功能使得查询更加快速。在最新的实现中,注册是广播到所有参与的实例上,在内存中保存所有条目,并在本地处理查询。

  Airbnb的SmartStack和Netflix的Eureka

  在本小节中,我们将会看一下两个定做的系统,它们是用来解决特定的需求。这并不意味着你不能或者不应该使用它们,你只是需要意识到它们。

  Airbnb的SmartStack是一个自动的服务发现和注册框架,透明地处理创建、删除、故障和维护工作。在容器运行的同一个宿主机上,SmartStack使用了两个分离的服务:Nerve(写到ZK)用于服务注册,Synapse(动态配置HAProxy)用于查询。这是一个针对非容器环境的完善的解决方案,随着实践推移,你会看到对于Docker,SmartStack也是有用的。

  Netflix的Eureka则不同,它运行在AWS环境中(Netflix全部运行在AWS上)。Eureka是一个基于REST的服务,用于定位服务以便负载均衡和中间件层服务器的故障迁移;Eureka还有一个基于Java的客户端组件,可以直接与服务交互。这个客户端有一个内置的负载均衡器,可以做基本的round-robin的负载均衡。在Netflix,Eureka用于做red/black部署、Cassandra和memcached部署、承载应用中关于服务的元数据。

  Eureka集群在参与的节点之间异步地复制服务注册信息;与ZK、etcd、Consul不同,Eureka相对于强一致性更倾向于服务可用性,让客户端自行处理过时的读请求,优点是在网络分区上更有弹性。你也知道的:网络是不可靠的。

  负载均衡

  服务发现的一个方面是负载均衡,有的时候负载均衡被认为是正交的,有的时候负载均衡被认为是服务发现的一部分。它可以在很多容器之间分散负载(入站服务请求)。在容器和微服务的语境下,负载均衡同时具有以下功能:

  • 最大化吞吐量,最小化响应时间
  • 防止热点(hotspotting),例如单一容器过载
  • 可以处理过度激进的DNS缓存(overly aggressive DNS caching)

  以下列举了一些有名的Docker中的负载均衡解决方案:

* NGINX
* HAProxy
* Bamboo
* Kube-Proxy
* vulcand
* Magnetic.io的vamp-router
* moxy
* HAProxy-SRV
* Marathon的servicerouter.py
* traefik

  如果你想了解更多关于负载均衡的信息,请查看Mesos meetup视频和nginx.conf 2014上关于NGINX和Consul的负载均衡的演讲。

  更多话题

  在本章的最后,我列举了服务发现解决方案的一览表。我并不想评出一个优胜者,因为我相信这取决于你的用例和需求。因此,把下表当做一个快速定位和小结:
2.png

  最后请注意:服务发现领域在不断变化中,每周都会出现新工具。例如,Uber最近开源了它的内部解决方案Hyperbahn,一个路由器的overlay网络,用来支持TChannel RPC协议。因为容器服务发现在不断发展,因此你可能要重新评估最初的选择,直到稳定下来为止。

  容器和编排

  就像上一章中介绍的那样,使用cattle方法来管理基础架构,你不必手动为特定应用分配特定机器。相反,你应该使用调度器来管理容器的生命周期。尽管调度是一个重要的活动,但是它其实只是另一个更广阔的概念——编排的一部分。

  从图5-1,可以看到编排包括健康检查(health checks)、组织原语(organizational primitives,如Kubernetes中的labels、Marathon中的groups)、自动扩展(autoscaling)、升级/回滚策略、服务发现。有时候base provisioning也被认为是编排的一部分,但是这已经超出了本书的讨论范围,例如安装Mesos Agent或Kubernetes Kubelet。

  服务发现和调度是同一枚硬币的两面。决定一个特定的容器运行在集群的哪个节点的实体,称之为调度器。它向其他系统提供了containers->locations的映射关系,可以以各种方式暴露这些信息,例如像etcd那样的分布式键值对数据库、像Mesos-DNS那样的DNS。
2.png

  图 5-1

  本章将从容器编排解决方案的角度讨论服务发现和网络。背后的动机很简单:假设你使用了某个平台,如Kubernetes;然后,你主要的关注点是平台如何处理服务发现和网络。

  注意:

  接下来,我会讨论满足以下两个条件的容器编排系统:开源的和相当大的社区。

  你可以看一下以下几个不同的解决方案:Fackebook的Bistro或者托管的解决方案,如Amazon EC2的容器服务ECS。

  另外,你如果想多了解一下分布式系统调度这个主题,我推荐阅读Google关于Borg和Omega的研究论文。

  在我们深入探讨容器编排系统之前,让我们先看一下编排的核心组件——调度器到底是做什么的。

  调度器到底是做什么的?

  分布式系统调度器根据用户请求将应用分发到一个或多个可用的机器上。例如,用户可能请求运行应用的100个实例(或者Kubernetes中的replica)。

  在Docker中,这意味着:(a)相应的Docker镜像存在宿主机上;(b)调度器告诉本地的Docker Daemon基于该镜像开启一个容器。

  在图5-2中,你可以看到,对于集群中运行的应用,用户请求了三个实例。调度器根据它对于集群状态的了解,决定将应用部署在哪里。比如,机器的使用情况、成功启动应用所必须的资源、约束条件(该应用只能运行在使用SSD的机器)等。进一步地,服务质量也是考量因素之一。
2.png

  图 5-2

  通过John Wilkes的Cluster Management at Google了解更多内容。

  警告:

  对于调度容器的限制条件的语义,你要有概念。例如,有一次我做了一个Marathon的demo,没有按照预期工作,原因是我没有考虑布局的限制条件:我使用了唯一的主机名和一个特定的角色这一对组合。它不能扩展,原因是集群中只有一个节点符合条件。同样的问题也可能发生在Kubernetes的label上。

  Vanilla Docker and Docker Swarm

  创造性地,Docker提供了一种基本的服务发现机制:Docker连接(links)。默认情况下,所有容器都可以相互通信,如果它们知道对方的IP地址。连接允许用户任何容器发现彼此的IP地址,并暴露端口给同一宿主机上的其他容器。Docker提供了一个方便的命令行选项--link,可以自动实现这一点。

  但是,容器之间的硬连接(hard-wiring of links)并不有趣,也不具扩展性。事实上,这种方法并不算好。长久来说,这个特性会被弃用。

  让我们看一下更好的解决方案(如果你仍然想要或者必须使用连接的话):ambassador模式。

  Ambassadors

  如图5-3所示,这个模式背后的想法是使用一个代理容器来代替真正的容器,并转发流量。它带来的好处是:ambassador模式允许你在开发阶段和生产阶段使用不同的网络架构。网络运维人员可以在运行时重写应用,而不需要更改应用代码。

  简单来说,ambassador模式的缺点是它不能有效地扩展。ambassador模式可以用在小规模的、手动的部署中,但是当你使用真正的容器编排工具(如Kubernetes或Apache Mesos)时,应该避免使用ambassador模式。
2.png

  图 5-3

  注意:

  如果你想要了解如何在Docker中部署ambassador模式,我再次建议你参考Adrian Mouat的书Using Docker。事实上,在图5-3中,我使用的就是他的amouat/ambassador镜像。

  Docker Swarm

  除了容器的静态链接(static linking),Docker有一个原生的集群工具Docker Swarm。Docker Swarm基于Docker API构建,并通过以下方式工作:有一个Swarm manager负载调度,在每一个宿主机上运行了一个agent,负责本地资源管理(如图5-4所示)。

  Swarm中有趣的地方在于,调度器是插件式的(plug-able),所以你可以使用除了内置以外的其他调度器,如Apache Mesos。在写本书的过程中,Swarm发布了1.0版本,并完成了GA(General Availability);新的特性(如高可用性)正在进行开发中。
2.png

  图 5-4

  网络

  在本书的第2章和第3章中,我们介绍了Docker中的单宿主机和多宿主机中的网络。

  服务发现

  Docker Swarm支持不同的后端:etcd、Consul和Zookeeper。你也可以使用静态文件来捕捉集群状态。最近,一个基于DNS的服务发现工具wagl被引入到了Swarm中。

  如果你想更多了解Docker Swarm,可以读一下Rajdeep Dua的幻灯片。

  Kubernetes

  Kubernetes(请看图5-5)是一个opinionated的开源框架,弹性地管理容器化的应用。简单来说,它吸取了Google超过十年的运行容器负载的经验,我们会简要地介绍一下。进一步地,你总是可以选择其他开源或闭源的方法来替换Kubernetes的默认实现,比如DNS或监控。
2.png

  图 5-5

  以下讨论假设你已经熟悉Kubernetes和它的技术。如果你还不熟悉Kubernetes的话,我建议看一下Kelsey HighTower的Kubernetes Up and Running。

  Kubernetes中,调度单元是一个pod,这是a tightly coupled set of containers that is always collocated。pod运行实例的数目是由Replication Controller定义和指定的。pods和services的逻辑组织是由labels定义的。

  在每个Kubernetes节点上,运行着一个称为Kubelet的agent,负责控制Docker daemon,向Master汇报节点状态,设置节点资源。Master节点提供API(例如,图5-6中的web UI),收集集群现在的状态,并存储在etcd中,将pods调度到节点上。
2.png

  图 5-6

  网络

  在Kubernetes中,每个pod都有一个可路由的IP,不需要NAT,集群节点上的pods之间也可以相互通信。pod中的所有容器共享一个端口命名空间(port namespace)和同一个notion localhost,因此没有必要做端口代理(port brokering)。这是Kubernetes的基本要求,可以通过network overlay来实现。

  在pod中,存在一个所谓的infrastructure容器,kubelet实例化的第一个容器,它会获得pod的IP,并设置网络命名空间。pod中的所有其他容器会加入到infra容器的网络和IPC命名空间。infra容器的网络启用了bridge模式(请参考第九页的bridge模式网络),pod中的所有其他容器使用container模式(请参考第11页的container模式网络)来共享它的命名空间。infra容器中的初始进程实际上什么也没做,因为它的目的只是提供命名空间的载体。关于端口转发的最近的work around可能在infra容器中启动额外的进程。如果infra容器死亡的话,那么Kubelet会杀死pod中所有的进程,然后重新启动进程。

  进一步地,Kubernetes的命名空间会启动所有control points。其中一个网络命名空间的例子是,Calico项目使用命名空间来强化coarse-grained网络策略(policy)。

  服务发现

  在Kubernetes的世界里,有一个服务发现的canonical抽象,这是service primitive。尽管pods随时可能启动和销毁因为他们可能失败(或者pods运行的宿主机失败),服务是长时间运行的:它们提供集群层面的服务发现和某种级别的负载均衡。它们提供了一个稳定的IP地址和持久化名字,compensating for the shortlivedness of all equally labelled pods。Kubernetes提供了两种发现机制:通过环境变量(限制于一个特定节点上)和DNS(集群层面上的)。

  Apache Mesos

  Apache Mesos (图5-7)是一个通用的集群资源管理器,抽象了集群中的各项资源(CPU、RAM等),通过这种方式,集群对于开发者来说就像一个巨大的计算机。

  在某种程度上,Mesos可以看作是分布式操作系统的内核。因此,它从未单独使用,总是和其他框架一起工作,例如Marathon(用于long-running任务,如web服务器)、Chronos(用于批量任务)或者大数据框架(如Apache Spark或Apache Cassandra)。
2.png

  图 5-7

  Mesos同时支持容器化负载(Docker容器)和普通的可执行文件(包括bash脚本、Python脚本、基于JVM的应用、一个纯粹的老的Linux二进制格式),也支持无状态和有状态的服务。

  接下来,我假设你已经熟悉了Mesos和它的相关技术。如果你不熟悉Mesos,我建议你阅读David Greenberg的书《Building Applications on Mesos》,该书介绍了Mesos,特别是对于分布式应用的开发者。

  如图5-8所示,你可以看到Marathon的UI,使用Apache Mesos来启动和管理长期运行的服务和应用。
2.png

  图 5-8

  网络

  网络特性和能力主要取决于Mesos使用的容器方案:

  对于Mesos containerizer来说,有一些要求,如Linux内核版本要大于3.16,并安装libnl。开启网络隔离功能之后,你可以构建一个Mesos Agent。启动之后,你可以看到以下输出:
  1. mesos-slave --containerizer=mesos --isolation=network/port_mapping --resources=ports:[31000-32000];ephemeral_ports: [33000-35000]
复制代码

  Mesos Agent会配置成使用非临时(non-ephemeral)端口31000-32000,临时(ephemeral)端口33000-35000。所有容器共享宿主机的IP地址,端口范围被分配到各个容器上(使用目的端口和容器ID之间的1:1映射)。通过网络隔离,你可以定义网络性能(如带宽),使得你能够监控容器的网络流量。更多细节,请看MesosCon 2015 Seattle上的演讲Per Container Network Monitoring and Isolation in Mesos。

  对于Docker containerizer,请看第二章。

  服务发现

  尽管Mesos不提供服务发现功能,但是有一个Mesos特定的解决方案,在praxis中也很常用:Mesos-DNS(Pure-Play DNS-based Solutions)。然而,还有其他的解决方案,如traefik。如果你对于Mesos中的服务发现很感兴趣,我们的文档中有一个专门的章节介绍它。

  Hashicorp Nomad

  Nomad是HashiCorp开发的集群调度器,HashiCorp也开发了Vagrant。Nomad于2015年9月份引入,主要目的是简化性。Nomad易于安装和使用。据说,它的调度器设计受到Google的Omega的影响,比如维护了集群的全局状态、使用优化的、高并发的调度器。

  Nomad的架构是基于agent的,它有一个单独的二进制文件,可以承担不同的角色,支持滚动升级(rolling upgrade)和draining nodes(为了重新平衡)。Nomad使用了一致性协议(强一致性)来实现所有的状态复制和调度,使用gossip协议来管理服务器地址,从而实现集群自动化(automatic clustering)和多区域联合(multiregion federation)。从图5-9可以看到,Nomad agent正在启动。
2.png

  图 5-9

  Jobs定义为HCL格式(HashiCorp-proprietary format)或JSON格式。Nomad提供了命令行接口和HTTP API,来和服务器进程进行交互。

  接下来,我假设你已经熟悉了Nomad和它的术语,否则我不建议你读这一节。如果你不熟悉的话,我建议你读一下Nomad: A Distributed, Optimistically Concurrent Schedule: Armon Dadgar, HashiCorp(这是HashiCorp的CTO Armon Dadgar对于Nomad的介绍)和Nomad的文档。

  网络

  Nomad中有几个所谓的任务驱动(task drivers),从通用的Java可执行程序到qemu和Docker。在之后的讨论中,我们将会专注后者。

  在写这篇文章的时候,Nomad要求Docker的版本为1.8.2,并使用端口绑定技术,将容器中运行的服务暴露在宿主机网卡接口的端口空间中。Nomad提供了自动的和手动的映射方案,绑定Docker容器的TCP和UDP协议端口。

  关于网络选项的更多细节,例如端口映射(mapping ports)和标签(labels),我建议你读一下该文章。

  服务发现

  在v0.2中,Nomad提供了基于Consul的服务发现机制,请参考相关文档。其中包含了健康检查(health checks),并假设运行在Nomad中的任务能够与Consul agent通信(使用bridge模式网络),这是一个挑战。

  我应该用哪个呢?

  以下内容当然只是我的一个建议。这是基于我的经验,自然地,我也偏向于我正在使用的东西。可能有各种原因(包括政治上的原因)导致你选择一个特定的技术。

  从纯粹的可扩展性的角度来看,这些选项有以下特点:
2.png

  对于少量的节点来说,自然是无所谓的:根据你的偏好和经验,选择以上四个选项中的任意一个都可以。但是请记住,管理大规模的容器是很困难的:

  • Docker Swarm可以管理1000个节点,请参考HackerNews和Docker的这篇博客。
  • Kubernetes 1.0据说可以扩展至100个节点,并在持续改进中,以达到和Apache Mesos同样的可扩展性。
  • Apache Mesos可以管理最多50000个节点。
  • 到目前为止,还没有资料表明Nomad可以管理多少个节点。

  从工作负载的角度来看,这些选项有以下特点:
2.png

  非容器化(Non-containerized)意味着你可以运行任何Linux Shell中可以运行的程序,例如bash或Python脚本、Java程序等。容器化(containerized)以为你需要生成一个Docker镜像。考虑到有状态服务,当今应用中的很大一部分需要一些调整。如果你想更多地学习编排工具,请参考:

  Docker Clustering Tools Compared: Kubernetes vs Docker Swarm

  O'Reilly Radar的Swarm v. Fleet v. Kubernetes v. Mesos

  为了完整性,我想介绍这个领域的一个新成员Firmament,这是一个非常不错的项目。这个项目的开发者们也是Google的Omega和Borg的贡献者,这个新的调度器将任务和机器组成了一个流网络,并运行最小成本优化(minimum-cost optimization)。特别有趣的一点是,Firmament不仅可以单独使用,也可以与Kubernetes和Mesos整合。

  容器的一天

  当选择使用哪种容器编排方案时,你需要考虑容器的整个生命流程。
2.png

  图 5-10

  Docker容器典型的生命周期包括以下几个阶段:

  阶段1: dev

  容器镜像(你的服务和应用)从一个开发环境中开始它的生命流程,通常是从一名开发者的笔记本开始的。你可以使用生产环境中运行的服务和应用的特性请求(feature requests)作为输入。

  阶段2: CI/CD

  然后,容器需要经历持续集成和持续发布(continuous integration and continous delivery),包括单元测试、整合测试和冒烟测试。

  阶段3: QA/staging

  然后,你可以使用一个QA环境(企业内部或云中的集群)和/或staging阶段。

  阶段4:prod

  最后,容器镜像是部署到生产环境中的。你也需要有一个策略去分发这些镜像。不要忘记以canaries的方式进行构建,并为核心系统(如Apache Mesos)、潜在的高层组件(如Marathon)和你的服务和应用的滚动式升级(rolling upgrade)做计划。

  在生产环境中,你会发现bugs,并收集相关指标,可以用来改善下一个迭代(返回到阶段1)。

  这里讨论的大部分系统(Swarm、Kubernetes、Mesos和Nomad)提供了很多指令、协议和整合点来覆盖这些阶段。然而,在你选择任何一个系统之前,你仍然需要端到端地试用该系统。

  社区是很重要的

  当选择编排系统时,另一个你需要考虑的一件方面是背后的社区。以下是一些指标:

  • 是否由一个正式的实体或流程来管理?例如,Apache软件基金会或Linux基金会。
  • 邮件列表、IRC频道、bug/issue追踪器、Git仓库(补丁或PR的数量)是否活跃?看一下历史情况,但是请特别注意活跃度。一个健康的社区至少会在其中一个领域非常活跃。
  • 该编排工具是否由一个单一实体实际控制?例如,对于Nomad来说,很明显的是,HashiCorp具有完全控制权。那么Kubernetes和Mesos呢?
  • 是否有多个独立的提供商提供支持?例如,你可以在多个不同的环境中运行Kubernetes或Mesos,并从很多商业或非商业的组织和个人得到帮助。

  我们已经到了本书的结尾。在容器的网络和服务发现方面,你已经学习到了不少知识。看完本章之后,你已经可以选择和部署容器化应用了。如果你希望深入地了解本书中的各个主体,请查看附录A,附录A提供了大量的资源列表。

  附录A 参考以下这些链接,有的包含了背景知识,有的包含了一些高级内容。

  网络参考


  服务发现参考


  其他高级话题


原文作者:Michael Hausenblas 来源:http://dockone.io/article/1521

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部