0717-7821348
彩乐乐网杀号

彩乐乐网杀号

您现在的位置: 首页 > 彩乐乐网杀号
彩乐乐遗漏网-从零开始入门 K8s| 详解 Pod 及容器规划形式
2019-09-27 22:07:08

一、为什么需求 Pod

容器的基本概念

咱们知道 Pod 是 Kubernetes 项目里边一个十分重要的概念,也是十分重要的一个原子调度单位,但是为什么咱们会需求这样一个概念呢?在运用容器 Docker 的时分,也没有这个说法。其实,假设想要了解 Pod,首要要了解容器,所以来回忆一下容器的概念:

容器的实质实践上是一个进程,是一个视图被阻隔,资源受限的进程。

容器里边 PID=1 的进程便是运用自身,这意味着办理虚拟机等于办理根底设施,由于咱们是在办理机器,但办理容器却等于直接办理运用自身。这也是之前说过的不可变根底设施的一个最佳表现,这个时分,你的运用就等于你的根底设施,它必定是不可变的。

在以上面的比方为条件的状况下,Kubernetes 又是什么呢?许多人都说 Kubernetes 是云年代的操作系统,这个十分有意思,由于假设以此类推,容器镜像便是这个操作系统的软件安装包,它们之间是这样的一个类比联系。

实在操作系统里的比方

假设说 Kubernetes 便是操作系统的话,那么无妨看一下实在的操作系统的比方。

比方里边有一个程序叫做 Helloworld,这个 Helloworld 程序实践上是由一组进程组成的,需求留意一下,这儿说的进程实践上等同于 Linux 中的线程。

由于 Linux 中的线程是轻量级进程,所以假设从 Linux 系统中去检查 Helloworld 中的 pstree,将会看到这个 Helloworld 实践上是由四个线程组成的,分别是 {api、main、log、compute}。也便是说,四个这样的线程一起协作,同享 Helloworld 程序的资源,组成了 Helloworld 程序的实在作业状况。

这是操作系统里边进程组或许线程组中一个十分实在的比方,以上便是进程组的一个概念。

那么咱们无妨考虑一下,在实在的操作系统里边,一个程序往往是依据进程组来进行办理的。Kubernetes 把它类比为一个操作系统,比方说 Linux。针关于容器咱们前面说到可以类比为进程,便是前面的 Linux 线程。那么 Pod 又是什么呢?实践上 Pod 便是咱们刚刚说到的进程组,也便是 Linux 里的线程组。

进程组概念

说到进程组,首要主张咱们至少有个概念上的了解,然后咱们再具体的解说一下。

仍是前面那个比方:Helloworld 程序由四个进程组成,这些进程之间会同享一些资源和文件。那么现在有一个问题:假设说现在把 Helloworld 程序用容器跑起来,你会怎样去做?

当然,最天然的一个解法便是,我现在就发动一个 Docker 容器,里边运转四个进程。但是这样会有一个问题,这种状况下容器里边 PID=1 的进程该是谁? 比方说,它应该是我的 main 进程,那么问题来了,“谁”又担任去办理剩余的 3 个进程呢?

这个中心问题在于,容器的规划自身是一种“单进程”模型,不是说容器里只能起一个进程,由于容器的运用等于进程,所以只能去办理 PID=1 的这个进程,其他再起来的进程其实是一个保管状况。 所以说服务运用进程自身就具有“进程办理”的才干。

比方说 Helloworld 的程序有 system 的才干,或许直接把容器里 PID=1 的进程直接改成 systemd,不然这个运用,或许是容器是没有办法去办理许多个进程的。由于 PID=1 进程是运用自身,假设现在把这个 PID=1 的进程给 kill 了,或许它自己运转过程中死掉了,那么剩余三个进程的资源就没有人收回了,这个是十分严峻的一个问题。

反过来,假设真的把这个运用自身改成了 systemd,或许在容器里边运转了一个 systemd,将会导致别的一个问题:使得办理容器不再是办理运用自身了,而等于是办理 systemd,这儿的问题就十分显着了。比方说我这个容器里边 run 的程序或许进程是 systemd,那么接下来,这个运用是不是退出了?是不是 fail 了?是不是呈现反常失利了?实践上是没办法直接知道的,由于容器办理的是 systemd。这便是为什么在容器里边运转一个杂乱程序往往比较困难的一个原因。

这儿再帮咱们整理一下:由于容器实践上是一个“单进程”模型,所以假设你在容器里发动多个进程,只需一个可以作为 PID=1 的进程,而这时分,假设这个 PID=1 的进程挂了,或许说失利退出了,那么其他三个进程就会天然而然的成为孤儿,没有人可以办理它们,没有人可以收回它们的资源,这是一个十分欠好的状况。

留意:Linux 容器的“单进程”模型,指的是容器的生命周期等同于 PID=1 的进程(容器运用进程)的生命周期,而不是说容器里不能创立多进程。当然,一般状况下,容器运用进程并不具有进程办理才干,所以你经过 exec 或许 ssh 在容器里创立的其他进程,一旦反常退出(比方 ssh 停止)是很简略变成孤儿进程的。

反过来,其实可以在容器里边 run 一个 systemd,用它来办理其他一切的进程。这样会发作第二个问题:实践上没办法直接办理我的运用了,由于我的运用被 systemd 给接管了,那么这个时分运用状况的生命周期就不等于容器生命周期。这个办理模型实践上是十分十分杂乱的。

Pod = “进程组”

在 Kubernetes 里边,Pod 实践上正是 Kubernetes 项目为你笼统出彩乐乐遗漏网-从零开始入门 K8s| 详解 Pod 及容器规划形式来的一个可以类比为进程组的概念。

前面说到的,由四个进程一起组成的一个运用 Helloworld,在 Kubernetes 里边,实践上会被界说为一个具有四个容器的 Pod,这个概念咱们必定要十分细心的了解。

便是说现在有四个责任不同、相互协作的进程,需求放在容器里去运转,在 Kubernetes 里边并不会把它们放到一个容器里,由于这儿会遇到两个问题。那么在 Kubernetes 里会怎样去做呢?它会把四个独立的进程分别用四个独立的容器发动起来,然后把它们界说在一个 Pod 里边。

所以当 Kubernetes 把 Helloworld 给拉起来的时分,你实践上会看到四个容器,它们同享了某些资源,这些资源都归于 Pod,所以咱们说 Pod 在 Kubernetes 里边只需一个逻辑单位,没有一个实在的东西对应说这个便是 Pod,不会有的。真实起来在物理上存在的东西,便是四个容器,这四个容器,或许说是多个容器的组合就叫做 Pod。而且还有一个概念必定要十分清晰,Pod 是 Kubernetes 分配资源的一个单位,由于里边的容器要同享某些资源,所以 Pod 也是 Kubernetes 的原子调度单位。

上面说到的 Pod 规划,也不是 Kubernetes 项目自己想出来的, 而是早在 Google 研制 Borg 的时分,就现已发现了这样一个问题。这个在 Borg paper 里边有十分十分清晰的描绘。简略来说 Google 工程师发现在 Borg 下面布置运用时,许多场景下都存在着类似于“进程与进程组”的联系。更具体的是,这些运用之前往往有着亲近的协作联系,使得它们有必要布置在同一台机器上而且同享某些信息。

以上便是进程组的概念,也是 Pod 的用法。

为什么 Pod 有必要是原子调度单位?

或许到这儿咱们会有一些问题:尽管了解这个东西是一个进程组,但是为什么要把 Pod 自身作为一个概念笼统出来呢?或许说能不能经过调度把 Pod 这个作业给处理掉呢?为什么 Pod 有必要是 Kubernetes 里边的原子调度单位?

下面咱们经过一个比方来解说。

假设现在有两个容器,它们是严密协作的,所以它们应该被布置在一个 Pod 里边。具体来说,第一个容器叫做 App,便是事务容器,它会写日志文件;第二个容器叫做 LogCollector,它会把刚刚 App 容器写的日志文件转发到后端的 ElasticSearch 中。

两个容器的资源需求是这样的:App 容器需求 1G 内存,LogCollector 需求 0.5G 内存,而当时集群环境的可用内存是这样一个状况:Node_A:1.25G 内存,Node_B:2G 内存。

假设说现在没有 Pod 概念,就只需两个容器,这两个容器要严密协作、运转在一台机器上。但是,假设调度器先把 App 调度到了 Node_A 上面,接下来会怎样样呢?这时你会发现:LogCollector 实践上是没办法调度到 Node_A 上的,由于资源不行。其实此刻整个运用自身就现已出问题了,调度现已失利了,有必要去从头调度。

以上便是一个十分典型的成组调度失利的比方。英文叫做:Task co-scheduling 问题,这个问题不是说不能解,在许多项目里边,这样的问题都有解法。

比方说在 Mesos 里边,它会做一个作业,叫做资源囤积(resource hoarding):即当一切设置了 Affinity 束缚的使命都达届时,才开端一致调度,这是一个十分典型的成组调度的解法。

所以上面说到的“App”和“LogCollector”这两个容器,在 Mesos 里边,他们不会说马上调度,而是等两个容器都提交完结,才开端一致调度。这样也会带来新的问题,首要调度功率会丢失,由于需求等候。由于需求等,还会有外一个状况会呈现,便是发作死锁,即相互等候的一个状况。这些机制在 Mesos 里都是需求处理的,也带来了额定的杂乱度。

另一种解法是 Google 的解法。它在 Omega 系统(便是 Borg 下一代)里边,做了一个十分杂乱且十分凶猛的解法,叫做达观调度。比方说:不论这些抵触的反常状况,先调度,一起设置一个十分精妙的回滚机制,这样经过抵触后,经过回滚来处理问题。这个办法相对来说要愈加高雅,也愈加高效,但是它的完成机制是十分杂乱的。这个有许多人也能了解,便是失望锁的设置必定比达观锁要简略。

而像这样的一个 Task co-scheduling 问题,在 Kubernetes 里,就直接经过 Pod 这样一个概念去处理了。由于在 Kubernetes 里,这样的一个 App 容器和 LogCollector 容器必定是归于一个 Pod 的,它们在调度时必定是以一个 Pod 为单位进行调度,所以这个问题是底子不存在的。

再次了解 Pod

在讲了前面这些知识点之后,咱们来再次了解一下 Pod,首要 Pod 里边的容器是“超亲密联系”。

这儿有个“超”字需求咱们了解,正常来说,有一种联系叫做亲密联系,这个亲密联系是必定可以经过调度来处理的。

比方说现在有两个 Pod,它们需求运转在同一台宿主机上,那这样就归于亲密联系,调度器必定是可以协助去做的。但是关于超亲密联系来说,有一个问题,即它有必要经过 Pod 来处理。由于假设超亲密联系赋予不了,那么整个 Pod 或许说是整个运用都无法发动。

什么叫做超亲密联系呢?大约分为以下几类:

  • 比方说两个进程之间会发作文件交流,前面说到的比方便是这样,一个写日志,一个读日志;
  • 两个进程之间需求经过 localhost 或许说是本地的 Socket 去进行通讯,这种本地通讯也是超亲密联系;
  • 这两个容器或许是微服务之间,需求发作十分频频的 RPC 调用,出于功能的考虑,也期望它们是超亲密联系;
  • 两个容器或许是运用,它们需求同享某些 Linux Namespace。最简略常见的一个比方,便是我有一个容器需求参加另一个容器的 Network Namespace。这样我就能看到另一个容器的网络设备,和它的网络信息。

像以上几种联系都归于超亲密联系,它们都是在 Kubernetes 中会经过 Pod 的概念去处理的。

现在咱们了解了 Pod 这样的概念规划,了解了为什么需求 Pod。它处理了两个问题:

  1. 咱们怎样去描绘超亲密联系;
  2. 咱们怎样去对超亲密联系的容器或许说是事务去做一致调度,这是 Pod 最首要的一个诉求。

二、Pod 的完成机制

Pod 要处理的问题

像 Pod 这样一个东西,自身是一个逻辑概念。那在机器上,它究竟是怎样完成的呢?这便是咱们要解说的第二个问题。

已然说 Pod 要处理这个问题,中心就在于怎样让一个 Pod 里的多个容器之间最高效的同享某些资源和数据。

由于容器之间本来是被 Linux Namespace 和 cgroups 离隔的,所以现在实践要处理的是怎样去打破这个阻隔,然后同享某些作业和某些信息。这便是 Pod 的规划要处理的中心问题所在。

所以说具体的解法分为两个部分:网络和存储。

1.同享网络

第一个问题是 Pod 里的多个容器怎样去同享网络?下面是个比方:

比方说现在有一个 Pod,其间包括了一个容器 A 和一个容器 B,它们两个就要同享 Network Namespace。在 Kubernetes 里的解法是这样的:它会在每个 Pod 里,额定起一个 Infra container 小容器来同享整个 Pod 的 Network Namespace。

Infra container 是一个十分小的镜像,大约 100~200KB 左右,是一个汇编语言写的、永久处于“暂停”状况的容器。由于有了这样一个 Infra container 之后,其他一切容器都会经过 Join 彩乐乐遗漏网-从零开始入门 K8s| 详解 Pod 及容器规划形式Namespace 的办法参加到 Infra container 的 Network Namespace 中。

所以说一个 Pod 里边的一切容器,它们看到的网络视图是彻底相同的。即:它们看到的网络设备、IP地址、Mac地址等等,跟网络相关的信息,其实满是一份,这一份都来自于 Pod 第一次创立的这个 Infra container。这便是 Pod 处理网络同享的一个解法。

在 Pod 里边,必定有一个 IP 地址,是这个 Pod 的 Network Namespace 对应的地址,也是这个 Infra container 的 IP 地址。所以咱们看到的都是一份,而其他一切网络资源,都是一个 Pod 一份,而且被 Pod 中的一切容器同享。这便是 Pod 的网络完成办法。

由于需求有鱼玄机一个相当于说中心的容器存在,所以整个 Pod 里边,必定是 Infra container 第一个发动。而且整个 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。这也是为什么在 Kubernetes 里边,它是答应去独自更新 Pod 里的某一个镜像的,即:做这个操作,整个 Pod 不会重建,也不会重启,这是十分重要的一个规划。

2.同享存储

第二问题:Pod 怎样去同享存储?Pod 同享存储就相对比较简略。

比方说现在有两个容器,一个是 Nginx,别的一个是十分一般的容器,在 Nginx 里放一些文件,让我能经过 Nginx 拜访到。所以它需求去 share 这个目录。我 share 文件或许是 share 目录在 Pod 里边是十分简略的,实践上便是把 volume 变成了 Pod level。然后一切容器,便是一切同归于一个 Pod 的容器,他们同享一切的 volume。

比方说上图的比方,这个 volume 叫做 shared-data,它是归于 Pod level 的,所以在每一个容器里可以直接声明:要挂载 shared-data 这个 volume,只需你声明晰你挂载这个 volume,你在容器里去看这个目录,实践上咱们看到的便是同一份。这个便是 Kubernetes 经过 Pod 来给容器同享存储的一个做法。

所以在之前的比方中,运用容器 App 写了日志,只需这个日志是写在一个 volume 中,只需声明挂载了相同的 volume,这个 volume 就可以马上被别的一个 LogCollector 容器给看到。以上便是 Pod 完成存储的办法。

三、详解容器规划形式

现在咱们知道了为什么需求 Pod,也了解了 Pod 这个东西到底是怎样完成的。最终,以此为根底,具体介绍一下 Kubernetes 十分发起的一个概念,叫做容器规划形式。

举例

接下来将会用一个比方来给咱们进行解说。

比方我现在有一个十分常见的一个诉求:我现在要发布一个运用,这个运用是 JAVA 写的,有一个 WAR 包需求把它放到 Tomcat 的 web APP 目录下面,这样就可以把它发动起来了。但是像这样一个 WAR 包或 Tomcat 这样一个容器的话,怎样去做,怎样去发布?这儿边有几种做法。

  • 第一种办法:可以把 WAR 包和 Tomcat 打包放进一个镜像里边。但是这样带来一个问题,便是现在这个镜像实践上揉进了两个东西。那么接下来,无论是我要更新 WAR 包仍是说我要更新 Tomcat,都要从头做一个新的镜像,这是比较费事的;
  • 第二种办法:便是镜像里边只打包 Tomcat。它便是一个 Tomcat,但是需求运用数据卷的办法,比方说 hostPath,从宿主机上把 WAR 包挂载进咱们 Tomcat 容器中,挂到我的 web APP 目录下面,这样把这个容器启用起来之后,里边就能用了。

但是这时会发现一个问题:这种做法必定需求保护一套分布式存储系统。由于这个容器或许第一次发动是在宿主机 A 上面,第2次从头发动就或许跑到 B 上去了,容器它是一个可搬迁的东西,它的状况是不坚持的。所以有必要保护一套分布式存储系统,使容器不论是在 A 仍是在 B 上,都可以找到这个 WAR 包,找到这个数据。

留意,即便有了分布式存储系统做 Volume,你还需求担任保护 Volume 里的 WAR 包。比方:你需求独自写一套 Kubernetes Volume 插件,用来在每次 Pod 发动之前,把运用发动所需的 WAR 包下载到这个 Volume 里,然后才干被运用挂载运用到。

这样操作带来的杂乱程度仍是比较高的,且这个容器自身有必要依靠于一套耐久化的存储插件(用来办理 Volume 里的 WAR 包内容)。

InitContainer

所以咱们有没有考虑过,像这样的组合办法,有没有愈加通用的办法?哪怕在本地 Kubernetes 上,没有分布式存储的状况下也能用、能玩、能发布。

实践上办法是有的,在 Kubernetes 里边,像这样的组合办法,叫做 Init Container。

仍是相同一个比方:在上图的 yaml 里,首要界说一个 Init Container,它只做一件作业,便是把 WAR 包从镜像里复制到一个 Volume 里边,它做完这个操作就退出了,所以 Init Container 会比用户容器先发动,而且严厉依照界说次序来顺次履行。

然后,这个要害在于刚刚复制到的这样一个意图目录:APP 目录,实践上是一个 Volume。而咱们前面说到,一个 Pod 里边的多个容器,它们是可以同享 Volume 的,所以现在这个 Tomcat 容器,仅仅打包了一个 Tomcat 镜像。但在发动的时分,要声明运用 APP 目录作为我的 Volume,而且要把它们挂载在 Web APP 目录下面。

而这个时分,由于前面运转过了一个 Init Container,现已履行完复制操作了,所以这个 Volume 里边现已存在了运用的 WAR 包:便是 sample.war,肯定现已存在这个 Volume 里边了。比及第二步履行发动这个 Tomcat 容器的时分,去挂这个 Volume,必定能在里边找到前面复制来的 sample.war。

所以可以这样去描绘:这个 Pod 便是一个自包括的,可以把这一个 Pod 在全世界任何一个 Kubernetes 上面都顺畅启用起来。不必忧虑没有分布式存储、Volume 不是耐久化的,它必定是可以发布的。

所以这是一个经过组合两个不同人物的容器,而且依照一些像 Init Container 的编列办法,一致去打包这样一个运用,把它用 Pod 往来不断做的十分典型的一个比方。像这样的一个概念,在 Kubernetes 里边便是一个十分经典的容器规划形式,叫做:“Sidecar”。

容器规划形式:Sidecar

什么是 Sidecar?便是说其实在 Pod 里边,可以界说一些专门的容器,来履行主事务容器所需求的一些辅佐作业,比方咱们前面举的比方,其实就干了一个事儿,这个 Init Container,它便是一个 Sidecar,它只担任把镜像里的 WAR 包复制到同享目录里边,以便被 Tomcat 可以用起来。

其它有哪些操作呢?比方说:

  • 本来需求在容器里边履行 SSH 需求干的一些作业,可以写脚本、一些前置的条件,其实都可以经过像 Init Container 或许别的像 Sidecar 的办法去处理;
  • 当然还有一个典型比方便是我的日志搜集,日志搜集自身是一个进程,是一个小容器,那么就可以把它打包进 Pod 里边去做这个搜集作业;
  • 还有一个十分重要的东西便是 Debug 运用,实践上现在 Debug 整个运用都可以在运用 Pod 里边再次界说一个额定的小的 Container,它可以去 exec 运用 pod 的 namespace;
  • 检查其他容器的作业状况,这也是它可以做的作业。不再需求去 SSH 登陆到容器里去看,只需把监控组件装到额定的小容器里边就可以了,然后把它作为一个 Sidecar 发动起来,跟主事务容器进行协作,所以相同事务监控也都可以经过 Sidecar 办法往来不断做。

这种做法一个十分显着的优势便是在于其实将辅佐功能从我的事务容器解耦了,所以我就可以独立发布 Sidecar 容器,而且更重要的是这个才干是可以重用的,即相同的一个监控 Sidecar 或许日志 Sidecar,可以被全公司的人共用的。这便是规划形式的一个威力。

Sidecar:运用与日志搜集

接下来,咱们再具体细化一下 Sidecar 这样一个形式,它还有一些其他的场景。

比方说前面说到的运用日志搜集,事务容器将日志写在一个 Volume 里边,而由于 Volume 在 Pod 里边是被同享的,所以日志容器 —— 即 Sidecar 容器必定可以经过同享该 Volume,直接把日志文件读出来,然后存到长途存储里边,或许转发到别的一个比方。现在业界常用的 Fluentd 日志进程或日志组件,基本上都是这样的作业办法。

Sidecar:署理容器

Sidecar 的第二个用法,可以称作为署理容器 Proxy。什么叫做署理容器呢?

假设现在有个 Pod 需求拜访一个外部系统,或许一些外部服务,但是这些外部系统是一个集群,那么这个时分怎样经过一个一致的、简略的办法,用一个 IP 地址,就把这些集群都拜访到?有一种办法便是:修正代码。由于代码里记录了这些集群的地址;别的还有一种解耦的办法,即经过 Sidecar 署理容器。

简略说,独自写一个这么小的 Proxy,用来处理对接外部的服务集群,它对外露出出来只需一个 IP 地址就可以了。所以接下来,事务容器首要拜访 Proxy,然后由 Proxy 去衔接这些服务集群,这儿的要害在于 Pod 里边多个容器是经过 localhost 直接通讯的,由于它们同归于一个 network Namespace,网络视图都相同,所以它们俩通讯 localhost,并没有功能损耗。

所以说署理容器除了做了解耦之外,并不会下降功能,更重要的是,像这样一个署理容器的代码就又可以被全公司重用了。

Sidecar:适配器容器

Sidecar 的第三个规划形式 —— 适配器容器 Adapter,什么叫 Adapter 呢?

现在事务露出出来的 API,比方说有个 API 的一个格局是 A,但是彩乐乐遗漏网-从零开始入门 K8s| 详解 Pod 及容器规划形式现在有一个外部系统要去拜访我的事务容器,它只知道的一种格局是 API B ,所以要做一个作业,便是把事务容器怎样想办法改掉,要去改事务代码。但实践上,你可以经过一个 Ada彩乐乐遗漏网-从零开始入门 K8s| 详解 Pod 及容器规划形式pter 帮你来做这层转化。

有个比方:现在事务容器露出出来的监控接口是 /metrics,拜访这个容器的 metrics 的 URL 就可以拿到了。但是现在,这个监控系统升级了,它拜访的 URL 是 /health,我只认得露出出 health 健康检查的 URL,才干去做监控,metrics 不认识。那这个怎样办?那就需求改代码了,但可以不去改代码,额定写一个 Adapter,用来把一切对 health 的这个恳求转发给 metrics 就可以了,所以这个 Adapter 对外露出的是 health 这样一个监控的 URL,这就可以了,你的事务就又可以作业了。

这样的要害,还在于 Pod 之中的容器是经过 localhost 直接通讯的,所以没有功能损耗,而且这样一个 Adapter 容器可以被全公司重用起来,这些都是规划形式给咱们带来的优点。

本文总结

  • Pod 是 Kubernetes 项目里完成“容器规划形式”的中心机制;
  • “容器规划形式”是 Google Borg 的大规模容器集群办理最佳实践之一,也是 Kubernetes 进行杂乱运用编列的根底依靠之一;
  • 一切“规划形式”的实质都是:解耦和重用。

最终

Pod 与容器规划形式是 Kubernetes 系统里边最重要的一个根底知识点,期望读者可以细心揣摩和把握。在这儿,我主张你去从头审视一下之前自己公司或许团队里运用 Pod 办法,是不是或多或少选用了所谓“富容器”这种规划呢?这种规划,仅仅一种过渡形状,会培养出许多十分欠好的运维习气。我强烈主张你逐步选用容器规划形式的思维,对富容器进行解耦,将它们拆分红多个容器组成一个 Pod。这也正是当时阿里巴巴“全面上云”战争中正在全力推动的一项重要的作业内容。

---------------------------------------

本文作者:张磊

原文链接:https://yq.aliyun.com/articles/718827?utm_content=g_1000077419

本文为云栖社区原创内容,未经答应不得转载。