Kubernetes系列学习文章——深入理解Pod(四)

发布于:2024-10-24 编辑:匿名 来源:网络

|简介 从本文开始,我们将详细介绍K8S的各个组件。学习一门技术,理论是第一位的。

只有充分理解底层原理,才能对以后的维护和调优有思路。 1.Pod1是什么。

Pod 是什么?我们在第二篇文章中有一个一般性的解释。你可以把它想象成一个“pod”,它包含一组相关的“bean”(容器)。

豆荚里的豆子一起吸收相同的营养,豆荚也是如此。内部的容器共享同一组资源。

K8S 官方文档将 Pod 描述为:Pod 是 Kubernetes 的基本构建块——您创建或部署的 Kubernetes 对象模型中最小、最简单的单元。 Pod 代表集群上运行的进程。

翻译成中文:Pod 是 K8S 的基本构建块,它是您可以在 K8S 集群中创建或部署的最小、最简单的单元。刚学K8S的同学普遍认为容器Docker是最小的单位。

事实上,事实并非如此。 Pod是K8S中最小的单元。

事实上,如果你把 Pod 看作虚拟机,把容器看作虚拟机中的应用程序,你就会明白很多。我们通过 K8S 与 OpenStack 的对比可以了解到: K8S OpenStackPodVMDockerapplication 应用程序和进程 OpenStack 管理的 VM 可以说是 OpenStack 中的最小单元。

我们知道虚拟机是隔离的。部署在其中的应用程序仅在虚拟机中运行,并且它们共享该虚拟机的CPU、Mem、网络和存储资源。

嗯,Pod 也是如此。 Pod 中的容器共享 Pod 中的 CPU、Mem、网络和存储资源。

那么 Pod 是如何做到的呢?看看下面的知识点你就明白了。 2、Pod的特点。

簇中的最小单元。注意,K8S集群的最小单位是Pod,而不是容器; K8S直接管理Pod,而不是容器。

一个 Pod 中可以运行一个或多个容器。如果一个 Pod 中只运行一个容器,则为“one-container-per-Pod”模式。

当然,你也可以将一组相关的容器放在一个Pod中。这些容器共享同一组网络命名空间和存储卷。

比如K8S官网文档给出了一个例子,将Web Server容器和File Puller容器放在一起(Web Server对外提供Web服务,File Puller负责内容存储和提供),然后就形成了一个 Pod。构成统一的服务单位。

另外,我们还可以考虑一些更复杂的东西,比如我们在传统运维中搭建的LAMP环境。如果我们将它们容器化部署在K8S集群中,会是一个什么样的模式呢? LAMP环境部署在IAAS层。

如果规模较小,可以使用一台虚拟机来部署apache+mysql+php环境。如果规模大的话,会使用更多的虚拟机并分布。

那么如果是PAAS层Pod容器部署呢?是不是apache+mysql+php分别跑一个docker,然后打包成一个Pod,然后如果规模大了,还要再建几个Pod? Pod 中的容器共享相同的网络和存储资源。在K8S中,每个Pod都会被分配一个唯一的IP地址,然后里面的容器会共享这个网络空间,其中包括IP地址和网络端口。

Pod 容器使用 localhost 进行内部通信。如果要与外部通信,则需要使用共享的IP和端口。

一个Pod可以指定一个共享存储Volume,然后该Pod中的所有容器都有权限访问这个Volume。 Volume用于持久化数据,Pod重启不会影响Volume中的数据。

Pod 中的容器共享相同的依赖关系。前面提到,具有相关关系的容器可以放在一个 Pod 中。

那么如何理解这里的关系呢?通常,我们在 Pod 中部署紧密耦合的服务,例如 LAMP 应用程序堆栈。这里的目的是实现联合调度、协调管理。

因此,最好不要将不相关的容器放入Pod中。如果没有规则地随意放置,你将无法体会到K8S的强大之处——编排。

二、Pod内部机制 1、Pod实现原理 我们在学习容器的时候了解到Linux提供了Namespace和Cgroup两种机制,这使得容器的出现成为可能。命名空间用于隔离进程,Cgroup用于控制进程资源的使用;命名空间由主机名、PID、文件系统、网络和 IPC 组成。

在K8S中,Pod的生成也是基于Namespace和Cgroup,所以我们可以用下图来说明Pod内部的架构综合: Pod架构。这些元素通过什么机制组合在一起?这是通过名为 Pause (gcr.io/google_containers/pause-amd64) 的容器来完成的。

K8S初始化Pod时,会先启动一个名为Pause的容器,然后再启动用户自定义的业务容器。我们将这个 Pause 容器视为“根容器”。

它有两个主要功能:扮演PID 1的角色,处理僵尸进程,并为Pod中的其他容器共享Linux命名空间的基础。首先我们来了解一下Linux系统下的PID 1。

过程、作用和意义。在Linux中,PID为1的进程称为超级进程,也称为根进程。

它是系统中的第一个进程,也是其他进程的父进程。所有进程都会挂在这个进程下。

如果子进程的父进程退出,子进程就会被挂起在PID 1下。其次,我们知道容器本身就是一个进程。

在Namespace中,Pause作为PID为1的进程存在于Pod中,其他业务容器挂载在这个Pause进程下。这样,一个Namespace下的进程就会以Pause为根,Pod下会存在一个树状结构。

最后,Pause还有一个函数,负责处理僵尸进程。僵尸进程:进程使用fork函数创建子进程。

如果子进程退出而父进程没有来得及调用wait或waitpid来获取其子进程的状态信息,那么子进程的描述符仍然保存在系统中,并且其进程号将一直存在并被占用(并且系统的进程数是有限的)。这种进程称为僵尸进程(以Z开头)。

Pause的容器代码是用C编写的(见下面的代码)。在Pause代码中,有一个for(;;)函数的无限循环。

函数中执行pause()函数。 Pause() 函数本身处于休眠状态,直到被信号中断。

因此,正是因为这种机制,Pause容器才会一直等待SIGCHLD信号。一旦有SIGCHLD信号(进程终止或停止时会发出该信号),Pause就会启动sigreap方法,sigreap方法会调用waitpid来获取其子进程的状态信息,这样僵尸进程就会不会在 Pod 中生成。

代码语言:c copy ## 暂停代码 static void sigdown(intsigno) { psignal(signo, "正在关闭,收到信号"); exit(0);}static void sigreap(int signo) { while (waitpid(-1 , NULL, WNOHANG) > 0);}int main() { if (getpid() != 1) /* 不是错误,因为暂停看到在基础设施容器之外使用。 */ fprintf(stderr, "警告:暂停应该是第一个进程\n"); if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) 返回 1; if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown) }, NULL) < 0) 返回 2; if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, .sa_flags = SA_NOCLDSTOP}, NULL) < 0) return 3;## 注意下面的 for 循环代码 for (;;)pause(); fprintf(stderr, "错误: 无限循环终止\n");返回 42;}2. Pod 的生命周期。

我们来分析一下Pod的生命周期。在K8S官方文档中,分别传递phase、Conditions、Container探针s、容器状态、Pod 就绪门、重启策略、Pod 生命周期等方面来描述 2.1 Pod 的状态值有哪些——phasePod 的状态由 PodStatus 对象中的 Phase 字段表示。

该阶段字段有以下值: 阶段名称 描述 PendingK8S 集群中已发起 Pod 创建请求,且其中的 Pod 还没有容器。该阶段通常发生在 Pod 被调度或 Pod 中的镜像被下载之前。

RunningPod已经被安排部署在一个Node中,并且里面的容器也已经创建好了。内部至少有一个容器正在运行或正在启动或重新启动。

SucceededPod 中的所有容器都成功运行,并且没有发生重启。 FailedPod 中的所有容器都会终止,并且至少有一个容器以失败方式终止。

换句话说,容器要么以非零状态退出,要么被系统终止。未知 由于某些原因,无法获取Pod的状态。

通常与 Pod 通信时会出现错误。 2.2 Pod Conditions 前面提到,PodStatus对象有一个phase字段,所以PodStatus也包含Pod Conditions。

这是一个数组,它包含的属性有: 字段 描述 lastProbeTime 上次检测到 Pod Condition 的时间戳。 lastTransitionTime 条件上次从一种状态转换到另一种状态的时间。

message 最后一次 Condition 状态转换的详细描述。 reasonCondition 上次转换的原因。

statusCondition 状态类型,可以是“True”、“False”、“Unknown”。 typeCondition 类型,包括以下几个方面: - PodScheduled(Pod 已被调度到其他节点) - Ready(Pod 可以提供服务请求,可以添加到所有匹配服务的负载均衡池中) - 已初始化(所有 init 容器已成功启动) - 不可调度(调度程序现在无法调度 Pod,例如由于缺乏资源或其他限制;) - ContainersReady( Pod中的所有容器都已经是就绪状态) 2.3 Containerprobesprobes中文意思是探针,所以containerprobes翻译成中文就是“容器探针”。

这是K8S诊断容器状态的一种机制。我们知道kubelet进程运行在Node中。

它的功能之一是收集容器的状态,然后报告给主节点。 “容器探测”机制是通过kubelet实现的。

那么kubelet是如何知道节点中容器的状态信息的呢?具体来说,kubelet 会调用容器提供的三个处理程序(钩子): ExecAction:在容器内执行指定的命令。如果命令退出且状态码为0,则认为诊断成功并且容器正常。

TCPSocketAction:通过容器的 IP 地址和端口号执行 TCP 检查。如果端口存在,则认为诊断成功,容器健康。

HTTPGetAction:通过容器的IP地址、端口号和路径调用HTTP GET方法。如果响应状态码大于等于且小于,则容器状态被认为是健康的。

每个Container探测都会得到三个结果: Success:容器通过诊断。失败:容器诊断失败。

未知:诊断失败,不应采取任何措施。另外,kubelet在运行的Container中可以有两种探测方法: livenessProbe:活性探测。

这是为了表明容器是否正在运行,服务是否正常。如果 LivenessProbe 检测到容器不健康,kubelet 会杀死该 Container,并根据 Container 的重启策略重新启动它。

如果Container没有提供livenessProbe,则默认状态为Success。 readinessProbe:就绪探针,这个是指示Container是否准备好提供服务(是否启动成功)。

如果 readinessProbe 检测失败,则 Container 的 Ready 将为 False,控制器会将该 Pod 的 Endpoint 从对应服务的 Endpoint 列表中移除,并且不再在该 Pod 上调度任何请求,直到下次检测成功。如果Container没有提供readinessProbe,则默认状态为Success。

之所以有这两种探测机制,主要是因为POD的生命周期会受到很多环境条件的影响,比如POD内部各个容器的状态,容器所依赖的上游或者外围服务的状态,因此,需要有一种机制来根据容器的不同状态来判断POD是否健康。因此,创建了活性和就绪检测来解决这个问题。

下面我们用两个动态图来介绍这两个Probe的具体工作方式(图片借用网络,入侵删除): 举个例子,如果一个Pod被LivenessProbe检测到,发现它无法再提供服务,那么LivenessProbe会根据容器的重启策略来决定。策略通过后是否重启并执行新的Pod替换操作。

Liveness探针机制工作动态图 有时有些应用程序需要一段时间来预热和启动。例如,后端项目的启动需要启动消息队列或数据库才能提供服务。

在这种情况下,使用就绪探针更为合适。准备就绪探测机制工作的动态图。

那么在具体的生产环境实践中,什么时候应该使用Liveness呢?何时使用准备状态?这里总结了一些经验,大家可以参考一下: (1)Liveness和Readiness应该直接检测程序,不要使用间接的检测方法。 (2) Liveness检测程序中不要做任何其他逻辑。

简单来说就是检测服务是否正常运行。如果主线程正常则直接返回,如果不正常则返回5xx。

如果存在其他逻辑,检测程序就会不准确。 (3)Readiness检测到的程序具有相关的处理逻辑。

Readiness主要检测并判断容器是否准备好对外提供服务。因此,实现一些逻辑来检查目标程序后端上所有依赖组件的可用性非常重要。

在实现就绪检测时,需要清楚地知道被检测的程序依赖于哪些函数,以及这些依赖函数何时就绪。例如,如果应用程序需要先建立与数据库的连接才能提供服务,那么在“Readiness”处理程序中,必须检查与数据库的连接是否已经建立,以最终确认程序是否准备就绪。

(4)Readiness不宜嵌套使用。也就是说,一个程序已经使用了Readiness来进行检测,所以不需要在外面再添加一层Readiness。

最后,Liveness和Readiness的YAML配置语法是相同的,也就是说,如果将Liveness设置为Readiness,则可以使用相同的YAML配置文件。 2.4 Container States Container States 一旦 Pod 落地 Node 创建完成,kubelet 就会在 Pod 中创建一个容器。

K8S 中容器有三种状态:等待、运行和终止。如果想要检查容器的状态,我们可以使用命令 kubectl describe pod [POD_NAME]。

该命令将显示 Pod 中每个容器的状态。另外,K8S创建资源对象时,可以利用生命周期来管理容器在运行和关闭之前的一些动作。

Lifecycle 有两个回调函数: PostStart:容器创建成功后,是预运行任务,用于资源部署、环境准备等 PreStop:容器终止前的任务,用于优雅关闭应用程序,通知其他等待:这是容器的默认状态。如果容器不处于“正在运行”或“已终止”状态,则它处于“等待”状态。

处于 Waiting 状态的容器仍然可以运行其所需的操作,例如拉取镜像、密钥等。在此状态下,Reason 字段会显示一些原因,指示其为何处于 Waiting 状态。

代码语言:javascript Copy... 状态:等待 原因:ErrImagePull...Running:表示容器正在运行。一旦容器进入运行状态,如果有postStart,就会执行。

另外,Started字段会显示容器启动时的具体时间代码。语言: javascript copy... 状态: 运行 开始: Wed, 30 Jan 16:46:38 0... 终止: 表示容器已终止。

当容器成功完成执行或由于某种原因失败时,就会出现此状态。显示容器终止的原因以及退出代码以及容器的开始和结束时间(如下例所示)。

另外,容器进入Termated之前,如果有preStop,就会被执行。代码语言:javascript 复制... 状态:已终止 原因:已完成 退出代码:0 开始:1 月 30 日星期三 11:45:26 0 完成:1 月 30 日星期三 11:45:26 0 ...2.5 Pod 生命周期控制方法 一般来说,Pod不会消失,除非控制器手动干预或删除它。

但是,例外情况是处于“成功”或“失败”状态的 Pod。如果处于该状态超过一定时间,比如termed-pod-gc-threshold的设置值,就会被垃圾回收机制清除。

注意:terminate-pod-gc-threshold用于master节点中设置gcTermination的阈值。默认值为 0 秒。

三类控制器控制 Pod 生命周期: Job:适合批量计算等一次性任务。任务完成后,Pod会被此类控制器清除。

Job的重启策略只能是“OnFailure”或“Never”。 ReplicationController、ReplicaSet 或 Deployment,此类控制器希望 Pod 保持运行,它们的重启策略只能是“始终”。

DaemonSet:每个节点一个 Pod。显然这类控制器的重启策略应该是“始终”。

3.Pod资源使用机制前面我们提到Pod就像一个虚拟机。我们可以给虚拟机分配固定的CPU、Mem、Disk、网络资源。

同理,Pod 也是如此。那么Pod如何使用和控制这些分配的资源呢?首先我们来了解一下CPU资源的分配方式:计算机中的CPU资源是以“时间片”的方式分配给请求的。

系统中的每一个操作都需要CPU处理。我们知道CPU的单位是Hz、GHz(1Hz=1/s,即单位时间内完成的振动次数,1GHz=1Hz=1次/s),频率越高,处理次数越多单位时间内完成的。

因此,任务申请的CPU时间片越多,获得的CPU资源就越多。其次我们来了解一下Cgroup中资源的一些换算单位: CPU换算单位 1 CPU = millicpu (1 Core = m) 0.5 CPU = millicpu (0.5 Core = m) 这里m表示millicore,millicore,在K8S集群中每个节点都可以确认通过操作系统命令获取该节点的CPU核心数,然后乘以该数字即可得到该节点的CPU核心总数。

例如,如果一个节点有四个核心,则该节点的CPU总数为m。如果要使用0.5核心,则需要*0.5 = m。

在K8S中,以下两个参数用于限制和请求CPU资源:spec.containers[].resources.limits.cpu 可以短暂超出CPU上限,容器不会被停止。可能会超出spec.containers[].resources.requests.cpu CPU请求值(K8S调度算法中的基础值)。

这里需要理解的是,如果resources.requests.cpu设置的值大于集群中每个Node的最大CPU核数,那么这个Pod就不会被调度(很容易理解就是没有Node)可以满足)。例如,我们在 YAML 中定义一个容器 CPU 资源如下: 代码语言:yaml copy resources: requests: memory: 50Mi cpu: 50m Limits: memory: Mi cpu: m 这里我们给出的 CPU 是 50m,即 0.05core 。

这0.05个核心占1个CPU资源时间的5%。另外,我们还需要知道K8S的CPU资源,这是一种可压缩的资源。

如果容器达到CPU设置值,就会开始限制,容器性能会下降,但不会终止并退出。最后我们来了解一下MEM的资源控制:单位换算:1 MiB = KiB。

请注意,MiB ≠ MB。 MB 是十进制单位,MiB 是二进制单位。

通常我们认为MB等于KB。事实上,1MB=KB,1MiB 等于 KiB。

中间带字母i的是国际电工委员会(IEC)根据产品制定的; KB、MB 和 GB 是国际单位制,基于产品。 K8S中的内存单元一般为Mi。

当然,你也可以使用Ki、Gi甚至Pi,这取决于具体的业务需求和资源容量。这里需要注意的是,内存不是可压缩的资源。

如果容器使用内存资源达到上限,就会发生OOM,导致内存溢出,容器会终止并退出。 3、Pod基本操作命令说明 具体命令创建 kubectl create -f xxx.yaml query kubectl get pod PodName / kubectl描述 pod PodName delete kubectl delete pod PodName update kubectl Replace /path/to/newPodName.yaml (当然也可以添加--force(强制替换))检查kubectllogsPodName命令的日志输出。

事实上,可能需要大量使用才能变得熟练。经过大量打字后,您将能够做到。

根据我个人的经验,计算机IT中的命令都离不开这些关键词:create、get、delete……当然,还有万能的--help。

Kubernetes系列学习文章——深入理解Pod(四)

站长声明

版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。

标签:

相关文章

  • 看见新力量NO.13|独家专访车麦&超电创始人孙泽锋

    看见新力量NO.13|独家专访车麦&超电创始人孙泽锋

    阿里云创新中心全新出品的《看见新力量》栏目,以访谈、直播的形式,探索创业者与企业创新背后的故事等多角度、多维度的价值报道,让您听到创业者真实的声音,看到科技创新的力量。 随着新能源汽车持续受到广泛关注,车企如何在新车型、新市场、新认知下提升销量,用户如何选

    06-18

  • 操盘手抖音生活服务“幕后”是谁?

    操盘手抖音生活服务“幕后”是谁?

    “万圣节活动已经开始,看好攻略,不要踏入陷阱!” 短视频,游乐园里已经挂满了南瓜灯,我们走进了一座骷髅形状的鬼屋,达人参观时在这里玩耍,他对着镜头仔细讲解攻略,最后引导大家点击点击左下角链接即可购买团购票。 效果非常好。 即便是只有一千名粉丝的达人,也很快在

    06-17

  • 上交所明确科创板上市公司重大资产重组审核标准及相关事项

    上交所明确科创板上市公司重大资产重组审核标准及相关事项

    上交所明确科创板上市公司重大资产重组审核标准及相关事项< titlesplit >科创板。 上市公司发行股份购买符合规定的资产,可申请“小额快速”审核机制,受理后不再进行审核询问,而是直接出具审核报告,报送上市公司并购重组委公司正在科创板接受审核。

    06-18

  • 中国网络电视台将引入战略投资者启动上市程序

    中国网络电视台将引入战略投资者启动上市程序

    据香港媒体报道,央视国际网络有限公司总经理汪文斌近日表示,公司正在进行股份制改革,将于近期上市未来;此外,公司还将引入战略投资者,并启动上市融资相关手续。   中国网络电视台开播一周年研讨会日前在京举行。 汪文斌表示,中国网络电视台在国家新媒体网络综合播控平

    06-18

  • K12教育是红海,作业盒完成2亿元B+轮融资,贝塔斯曼领投,

    K12教育是红海,作业盒完成2亿元B+轮融资,贝塔斯曼领投,

    NewSeed(ID:pelink)10月12日消息,K12教育品牌作业盒今日宣布完成2亿元B+轮融资。 ,本轮融资由贝塔斯曼领投,新世界、百度创投、好未来跟投。 本轮融资将主要用于推动“AIOC战略”的实施。 AIOC(AI-Oriented-Content)是指“基于自适应学习场景的内容构建”。 去年同期,

    06-18

  • 基因编辑公司博雅辑因已完成1亿元Pre-B轮融资,礼来亚洲基金领投,华盖资本跟投,

    基因编辑公司博雅辑因已完成1亿元Pre-B轮融资,礼来亚洲基金领投,华盖资本跟投,

    据投资界8月13日消息,博雅辑因集团(EdiGene Inc.)宣布完成Pre-B轮1亿元人民币。 B轮融资。 本轮融资由礼来亚洲基金领投,华盖资本跟投。 公司A轮领投方IDG资本、中国经济合作社、孔夫子等投资者持续投资。   博雅辑因公司成立于2007年,目前总部位于北京,在广州和美国

    06-18

  • 白酒品牌“谷小酒”获6300万元Pre-A轮融资,博江资本领投

    白酒品牌“谷小酒”获6300万元Pre-A轮融资,博江资本领投

    据投资界12月24日消息,白酒品牌谷小酒已完成1万元Pre-A轮融资。 本轮由博江资本领投,阿里巴巴合伙人王帅、中金汇财跟投。 此前,谷小酒公司还于今年3月获得真格基金、中金汇财等机构数万笔天使投资,9个月内完成总计近亿元融资。 谷小酒酒是一种浓香型酒,由五种谷物固态发

    06-18

  • 聚焦科创企业,南通设立产业投资基金

    聚焦科创企业,南通设立产业投资基金

    据投资界(ID:pedaily)了解,近日,通州湾示范区联合省沿海集团、南通创新发展基金,共同设立南通通州海湾示范区海金创业投资基金。 据悉,该基金规模3亿元,已签署投资协议并完成工商注册。 据介绍,该基金的设立将根据通州湾示范区“五园一城一基地”的产业定位和布局,

    06-18

  • AI芯片公司【墨芯】获1亿元Pre-A轮融资

    AI芯片公司【墨芯】获1亿元Pre-A轮融资

    墨芯近期完成智能互联网产业基金战略融资。 墨芯此次的战略投资者是智能互联网产业基金,该基金是由中国电信集团投资有限公司、中国互联网投资基金管理公司和前海方舟资产管理公司。 墨芯是一家AI芯片设计商,提供终端和云端AI芯片加速解决方案。 打造新一代AI计算引擎。 应用

    06-18

  • 是谁在悄悄“跳入”医美和巨头重金押注的新趋势?

    是谁在悄悄“跳入”医美和巨头重金押注的新趋势?

    最近一段时间,中国的医美和巨头又闲不住了。 11月8日,Amic与韩国光子市场份额最大的激光医疗器械公司Jeisys签署经销协议,获得Jeisys两款光电抗衰老设备在中国的推广、经销、销售及相关服务,成为*中国大陆经销商。 但这并不是一个孤立的案例。 此前,包括昊海生物、华东医

    06-18

  • 博世集团:2020年在华销售额1173亿元,同比增长9.1%

    博世集团:2020年在华销售额1173亿元,同比增长9.1%

    博世集团宣布,得益于中国汽车市场、消费品和工业技术市场的复苏,其在华业绩2020年逆势上扬,销售额达亿元,同比增长约9.1%。 截至2018年,博世在中国拥有超过53,000名员工,是德国以外博世员工数量最多的国家。 近10年来,博世在中国市场的投资已超过1亿元人民币。

    06-18

  • 中国一汽2月销量23.18万辆,同比增长379.2%

    中国一汽2月销量23.18万辆,同比增长379.2%

    中国一汽公告,根据2月产销数据,当月中国一汽生产整车21辆,同比增长.5%;销售整车81辆,同比增长0.2%。 其中,2月份红旗品牌整车产量6辆,同比增长0.0%;销售整车9辆,同比增长0.7%。 听,中小企业反馈平台。 倾听用户需求,倾听创业者声音,解决中小企业痛点。 点击立即参

    06-18