云計(jì)算時(shí)代操作系統(tǒng)Kubernetes之容器生命周期管理(上)

筆者在上一篇文章中介紹了如何在POD中部署多個(gè)容器實(shí)例,特別是我們通過(guò)部署邊車容器的這種方式,在不修改原始容器代碼的情況下,對(duì)容器的功能進(jìn)行了擴(kuò)充,比如提供HTTPS訪問(wèn)支持的能力等。坦白講,我們上篇文章中有很多細(xì)節(jié)信息未給大家做展開(kāi)介紹,比如容器的狀態(tài),Ready具體代表什么意思等,今天我們就來(lái)針對(duì)容器的生命周期進(jìn)行一次深入的探索。

當(dāng)我們將應(yīng)用部署到Kubernetes平臺(tái)上之后,我們可以通過(guò)kubectl describe命令來(lái)返回完整的POD對(duì)象。返回的POD對(duì)象有個(gè)status節(jié)點(diǎn),包含了對(duì)象的狀態(tài)信息,具體來(lái)講,這部分會(huì)包含如下的POD對(duì)象狀態(tài)信息:

1,POD對(duì)象的IP地址和POD被調(diào)度到的工作節(jié)點(diǎn)。

2,POD是什么時(shí)候啟動(dòng)的。

3,POD的QOS(Quality of service)類型。

4,POD當(dāng)前處于什么階段(phase)。

5,POD的當(dāng)前狀態(tài)(condition)以及POD中每個(gè)容器實(shí)例的狀態(tài)(state)。

我們本篇文章主要分析容器的生命周期,因此上邊的這些狀態(tài)中,我們會(huì)討論phase,condition和state,因?yàn)檫@些狀態(tài)是構(gòu)成POD和里邊運(yùn)行容器狀態(tài)的指示燈。首先我們從POD的生命周期來(lái)介紹,如下圖所示:

《圖1.1 POD的全生命周期phase圖》

如上圖所示,POD的整個(gè)生命周期中總共有5種phase階段,接下來(lái)我們來(lái)詳細(xì)介紹一下每種phase:

- Pending階段,是新建POD的初始情況,直到POD被調(diào)度到某個(gè)工作節(jié)點(diǎn),并且POD中的容器實(shí)例鏡像被下載到工作節(jié)點(diǎn)的緩存并啟動(dòng)起來(lái),那么POD將會(huì)一直處于Pending階段。

- Running階段,POD中至少有一個(gè)容器實(shí)例處于Running狀態(tài),那么POD就處于這個(gè)階段。

- Succeeded階段,當(dāng)POD中所有的容器實(shí)例都成功運(yùn)行并退出,那么POD就處于這個(gè)階段。

- Failed階段,當(dāng)POD中至少有一個(gè)容器實(shí)例因?yàn)槟撤N原因運(yùn)行失敗而出錯(cuò),那么POD就會(huì)被標(biāo)記為Failed階段。

- Unkown階段,當(dāng)Kublet停止向API Server匯報(bào)POD的狀態(tài)信息時(shí),POD就處于這個(gè)階段,造成這種情況的可能原因包括但不限于:工作節(jié)點(diǎn)故障,或者工作節(jié)點(diǎn)的網(wǎng)絡(luò)連接斷開(kāi)等。

通過(guò)分析POD所處的階段能快速獲知POD的實(shí)際運(yùn)行情況,接下來(lái)讓我們把yunpan.yaml這個(gè)POD重新啟動(dòng)起來(lái),來(lái)實(shí)戰(zhàn)一下上邊介紹的五種階段。首先我們?cè)谧约旱募荷现匦虏渴饄unpan.yaml這個(gè)對(duì)象,部署命令是:kubectl apply -f yunpan.yaml,接著我們來(lái)通過(guò)返回的POD對(duì)象信息找到status部分并輸出到控制臺(tái),通過(guò)命令:kubectl get po yunpan -o yaml | grep phase,在筆者的本地集群上,輸出如下:

?? kubernetes kubectl get po yunpan -o yaml | grep phase

? phase: Running

由于這個(gè)POD中只有一個(gè)容器,并且這個(gè)容器實(shí)例正在健康運(yùn)行,因此POD所處的階段是Running,和我們上邊分析的結(jié)果一致。我們也可以通過(guò)kubectl get pod yunpan來(lái)返回POD的運(yùn)行狀態(tài)信息,但是大家要注意的是,如果POD的狀態(tài)是健康的,那么返回的Status列就是POD的階段,但是對(duì)于出現(xiàn)運(yùn)行問(wèn)題的POD,這個(gè)Status列將顯示具體發(fā)生了什么類型的錯(cuò)誤,我們會(huì)在后面詳細(xì)介紹。

【POD的Conditions】

筆者在過(guò)往的容器化項(xiàng)目上,發(fā)現(xiàn)大家對(duì)POD的phase,status和conditions這三個(gè)屬性會(huì)出現(xiàn)混淆,本質(zhì)上這三個(gè)屬性描述的都是POD的狀態(tài)信息。phase可以看成是POD在經(jīng)歷某些調(diào)度和操作之后的結(jié)果,因此從phase上我們是看不出來(lái)POD經(jīng)歷了哪些操作或者異常,而condition字段向我們展示了這個(gè)細(xì)節(jié)信息。POD的conditions告訴我們POD從創(chuàng)建之初,具體經(jīng)歷過(guò)哪些階段,以及如果出現(xiàn)異常,具體是什么原因造成的。

因此conditions通常會(huì)有多個(gè)值,不像phase,每個(gè)POD在特定的時(shí)間點(diǎn),只會(huì)有一個(gè)phase值。我們先來(lái)看看Kubernetes的POD對(duì)象提供的四種類型的conditions:

- PodScheduled,表示POD是否被成功的調(diào)度到工作節(jié)點(diǎn)上。

- Initialized,POD中所有的初始化容器都成功的完成執(zhí)行。

- ContainersReady,POD中所有容器都已經(jīng)Ready。

- Ready,POD準(zhǔn)備好對(duì)外提供服務(wù),所有容器實(shí)例都報(bào)告狀態(tài)為Ready。

每個(gè)condition類型在POD的聲明周期中有滿足或者不滿足兩個(gè)狀態(tài),如下圖所示,在POD剛開(kāi)始啟動(dòng)的時(shí)候,PodScheduled和Initialized這兩個(gè)condition沒(méi)有滿足,但是很快就會(huì)滿足,并且會(huì)保持到整個(gè)POD的生命周期。而Ready和ContainerReady這兩個(gè)condition會(huì)在POD的整個(gè)生命周期中發(fā)生多次變化。

《圖1.2 conditions在POD的整個(gè)生命周期中的變化》

如上圖所示,POD對(duì)象有四個(gè)condition類型,并且在生命周期會(huì)發(fā)生變化。其實(shí)對(duì)于Kubernetes中的node對(duì)象(工作節(jié)點(diǎn))來(lái)說(shuō),它也有自己一套condition類型,MemoryPressure,DiskPressure,PIDPressure和Ready。這兩個(gè)不同類型對(duì)象condition的共性是都有Ready類型,其實(shí)大部分Kubernetes對(duì)象的condition類型都有Ready,表示事情按預(yù)期的在進(jìn)行這個(gè)意思。

接下來(lái)我們看看condition在POD對(duì)象上的真實(shí)輸出,筆者在自己本地集群上運(yùn)行kubectl describe pod yunpan,從輸出的超長(zhǎng)對(duì)象信息中,我們找到了condition部分,如下圖所示:

《圖1.3 POD的conditions信息》

從kubectl describe我們只能看到每個(gè)condition是否被滿足(true或者false),如果你想知道為什么一個(gè)狀態(tài)是false,可以在返回的對(duì)象信息中找.status.conditions字段,如下圖所示:


《圖1.4 每個(gè)condition詳細(xì)信息》

從上圖可以看出,每個(gè)condition類型都有status字段,表示這個(gè)condition具體是true還是false,以及unknow。對(duì)于筆者本地的yunpan這個(gè)pod來(lái)說(shuō),所有的condition類型都是true,這就說(shuō)明pod正在健康運(yùn)行,對(duì)外提供服務(wù)。每個(gè)condition類型也包含了一個(gè)reason字段,提供了更多關(guān)于狀態(tài)變化的信息。

我們從POD對(duì)象中也可以看到容器實(shí)例的狀態(tài),具體來(lái)說(shuō),state字段反映了容器當(dāng)前的狀態(tài),而lastState字段表示上一個(gè)容器的啟動(dòng)實(shí)例運(yùn)行結(jié)束時(shí)的狀態(tài)。容器狀態(tài)部分還包括一個(gè)ID字段containerID和鏡像imageID字段,這兩個(gè)字段不言自明。除了這些字段,容器狀態(tài)部分也有ready和restartCount字段,分別表示容器是否ready,以及重啟的次數(shù)。對(duì)于容器實(shí)例的狀態(tài)來(lái)說(shuō),最最重要的是state字段,一個(gè)容器實(shí)例的狀態(tài)機(jī)如下圖所示:

《圖1.5 容器的狀態(tài)機(jī)》

如上圖所示,容器的狀態(tài)機(jī)中有4中狀態(tài),對(duì)于每種狀態(tài)的詳細(xì)介紹如下:

- Waiting狀態(tài),容器實(shí)例在等待啟動(dòng),從reason和message字段可以得到處于這個(gè)狀態(tài)容器實(shí)例的更多信息。

- Running狀態(tài),容器實(shí)例已經(jīng)被創(chuàng)建,并且里邊的進(jìn)程正在運(yùn)行。startedAt字段記錄了容器被成功啟動(dòng)的時(shí)間。

- Terminated狀態(tài),容器中的進(jìn)程已經(jīng)運(yùn)行結(jié)束,startAt字段和finishedAt字段記錄了容器啟動(dòng)的時(shí)間和運(yùn)行結(jié)束的時(shí)間。exitCode字段記錄了主進(jìn)程退出的狀態(tài)碼。

- Unknow狀態(tài),容器的狀態(tài)無(wú)法判斷。

我們前邊通過(guò)kubectl get pods這個(gè)命令只能看到POD中有多少容器處于Ready狀態(tài),要獲取更加詳細(xì)的信息,我們可以使用kubectl describe,如下圖所示,在筆者的本地Kubernetes環(huán)境的輸出:

《圖1.6 容器的狀態(tài)信息》

到現(xiàn)在為止,我們創(chuàng)建的所有POD都很健康,運(yùn)行過(guò)程中沒(méi)有出現(xiàn)任何問(wèn)題,但是這并不是常態(tài),代碼會(huì)有缺陷,網(wǎng)絡(luò)會(huì)抖動(dòng)等,都可能造成POD中運(yùn)行的所有容器實(shí)例出現(xiàn)故障,對(duì)于生產(chǎn)級(jí)別的部署方案,我們必須有機(jī)制確保POD以及運(yùn)行在POD中的容器健康運(yùn)行,接下來(lái)我們來(lái)聊聊這個(gè)話題。

【保障容器實(shí)例健康運(yùn)行】

當(dāng)POD被調(diào)度到某個(gè)工作節(jié)點(diǎn)后,運(yùn)行在工作節(jié)點(diǎn)上的Kubelet會(huì)負(fù)責(zé)驅(qū)動(dòng)容器運(yùn)行時(shí)來(lái)啟動(dòng)容器實(shí)例,并盡自己最大的努力來(lái)讓容器長(zhǎng)時(shí)間健康運(yùn)行,直到運(yùn)行結(jié)束退出。如果容器中的主進(jìn)程因?yàn)槟撤N原因退出結(jié)束,那么Kublets會(huì)負(fù)責(zé)重啟容器。如果應(yīng)用在運(yùn)行過(guò)程中出現(xiàn)了錯(cuò)誤,Kubernetes會(huì)自動(dòng)重新啟動(dòng)POD,大大減輕了我們?yōu)榱舜_保應(yīng)用健康運(yùn)行所需要的運(yùn)維工作。我們來(lái)通過(guò)一個(gè)具體的例子,看看這一切是如何發(fā)生的。

在前邊的文章中,筆者介紹過(guò)如何在一個(gè)POD中通過(guò)邊車模式運(yùn)行多個(gè)容器,我們創(chuàng)建了yunpan-ssl這個(gè)POD,這個(gè)POD中包含兩個(gè)容器,我們的SpringCloud應(yīng)用程序容器負(fù)責(zé)提供業(yè)務(wù)服務(wù),而Envoy容器實(shí)例主要負(fù)責(zé)處理HTTPS流量請(qǐng)求。首先在自己的環(huán)境中將這個(gè)POD啟動(dòng)起來(lái),并通過(guò)port-forward創(chuàng)建本地訪問(wèn)服務(wù)的代理:kubectl port-forward yunpan-ssl 8085 8443 9901。

接下來(lái),我們要做的是讓Envoy容器crash異常退出,同時(shí)我們觀測(cè)Kubernetes是如何處理這種場(chǎng)景。新打開(kāi)一個(gè)終端運(yùn)行命令kubectl get pods -w,我們就能看到POD狀態(tài)變化的輸出信息,當(dāng)然我們也需要觀察產(chǎn)生的時(shí)間,請(qǐng)打開(kāi)第二個(gè)終端,運(yùn)行命令kubectl get events -w,好了,監(jiān)控窗口準(zhǔn)備好了,接下來(lái)我們想辦法讓Envoy容器退出。

如果你熟悉操作系統(tǒng)原理,應(yīng)該知道我們可以給進(jìn)程發(fā)送KILL信號(hào),來(lái)讓進(jìn)程退出(在macOS上或者Linux上執(zhí)行kill命令,背后的原理就是給進(jìn)程發(fā)送KILL信號(hào)),但是對(duì)于Envoy容器進(jìn)程,因?yàn)樗荘ID為1的進(jìn)程,而Linux操作系統(tǒng)不允許殺掉PID為1的進(jìn)程,我們得換個(gè)方法。你可能已經(jīng)想到了,我們可以登錄到宿主機(jī)上(對(duì)于minikube實(shí)例,宿主機(jī)就是這臺(tái)叫做minikube的虛擬機(jī),筆者有時(shí)候也稱之為工作節(jié)點(diǎn),因?yàn)閙inikube是一個(gè)單機(jī)環(huán)境,管理節(jié)點(diǎn)和工作節(jié)點(diǎn)都運(yùn)行在這臺(tái)叫minikube的虛擬機(jī)上)。

不過(guò)我們有更好的方法,而不需要登錄到虛擬機(jī)上,就是通過(guò)Envoy提供的管理接口,來(lái)遠(yuǎn)程通過(guò)API接口停止某個(gè)進(jìn)程的運(yùn)行。我們可以在命令行窗口執(zhí)行curl -X POST http://localhost:9901/quitquitquit ,接著我們就可以從POD的狀態(tài)監(jiān)控接口看到如下的輸出信息:

?? init-demo-image kubectl get pods -w

NAME? ? ? ? READY? STATUS? ? RESTARTS? AGE

yunpan-ssl? 2/2? ? Running? 0? ? ? ? ? 12m

yunpan-ssl? 1/2? ? NotReady? 0? ? ? ? ? 13m

yunpan-ssl? 2/2? ? Running? ? 1? ? ? ? ? 13m

從上邊的輸出可以看到,當(dāng)我們執(zhí)行了殺死Envoy容器進(jìn)程之后,POD的狀態(tài)馬上從Running就變成了NotReady,從輸出的Ready列也可以看到,2個(gè)容器實(shí)例中只有1個(gè)處于ready狀態(tài)。Kubernetes接下來(lái)會(huì)馬上重新啟動(dòng)Envoy容器實(shí)例,隨著容器實(shí)例的狀態(tài)ready,POD的狀態(tài)也從NotReady變成Running,并且RESTARTS列的計(jì)數(shù)加1,記錄了容器被重啟啟動(dòng)的次數(shù)。

注意:如果POD中的多個(gè)容器實(shí)例中,有任何一個(gè)運(yùn)行失敗,其他的容器實(shí)例不受影響,會(huì)繼續(xù)運(yùn)行。

接下來(lái)我們看看監(jiān)控時(shí)間窗口的輸出信息,如下所示:

?? kubernetes git:(master) kubectl get events -w

LAST SEEN? TYPE? ? ? REASON? ? ? ? ? ? ? ? ? ? OBJECT? ? ? ? ? ? MESSAGE

0s? ? ? ? ? Normal? ? Pulled? ? ? ? ? ? ? ? ? ? pod/yunpan-ssl? ? Container image "qigaopan/yunpan-ssl-proxy:v1.1" already present on machine

0s? ? ? ? ? Normal? ? Created? ? ? ? ? ? ? ? ? pod/yunpan-ssl? ? Created container envoy

0s? ? ? ? ? Normal? ? Started? ? ? ? ? ? ? ? ? pod/yunpan-ssl? ? Started container envoy

從輸出的事件信息中可以看到,envoy容器實(shí)例被重新啟動(dòng)了,你可以通過(guò)HTTPS來(lái)驗(yàn)證Envoy容器能夠正常接收請(qǐng)求并處理。從這個(gè)輸出中希望大家能看到非常重要的一點(diǎn),Kubernetes本質(zhì)上從來(lái)都沒(méi)有重啟的概念,而是刪除老的容器實(shí)例,并且重新創(chuàng)建一個(gè)容器實(shí)例來(lái)取代老的實(shí)例,但是為了符合大家的直覺(jué),我們還是叫restarting。

注:容器重新創(chuàng)建之后,進(jìn)程在運(yùn)行過(guò)程中寫(xiě)到容器文件系統(tǒng)的所有信息都會(huì)丟失,為了讓這些數(shù)據(jù)能夠在容器被重新創(chuàng)建后可用,我們需要給容器掛載存儲(chǔ)卷,筆者會(huì)在后續(xù)的文章中詳細(xì)介紹。另外需要注意的是,初始化容器不會(huì)因?yàn)閼?yīng)用實(shí)例容器實(shí)例的重啟而重新執(zhí)行,從這點(diǎn)可以看到,初始化容器的生命周期和POD一致。

Kubernetes也并不是在所有情況下都會(huì)默認(rèn)的重啟容器實(shí)例,我們可以通過(guò)restartPolicy這個(gè)字段來(lái)聲明POD的重啟策略,因?yàn)槿绻慌渲?,默認(rèn)情況下,Kubernetes不管三七二十一,無(wú)論是POD正常運(yùn)行結(jié)束還是異常情況,都會(huì)重啟容器實(shí)例,這在某些情況下可能和我們的預(yù)期不符。Kubernetes提供了三種重啟策略,如下圖所示:

《圖1.7 POD的容器重啟策略》

如上圖所示,Kubernetes提供了三種類型的重啟策略,詳細(xì)介紹如下:

- Always策略,容器任何情況下都會(huì)重啟,無(wú)論exitCode返回碼表示運(yùn)行成功還是失敗。如果不在POD的YAML文件中顯式的設(shè)置,默認(rèn)的重啟策略是Always。

- OnFailure策略,容器只會(huì)在返回碼未非0的時(shí)候重啟(對(duì)于大部分系統(tǒng)來(lái)說(shuō),非0都表示運(yùn)行出錯(cuò))。

- Never策略,從不重啟容器實(shí)例,即便是運(yùn)行出現(xiàn)錯(cuò)誤退出。

注:Kubernetes的重啟策略是配置在POD級(jí)別,這就意味著POD中所有的容器使用相同的重啟策略,我們無(wú)法為每個(gè)容器單獨(dú)配置重啟策略。

如果我們多次調(diào)用Envoy管理節(jié)點(diǎn)提供的/quitquitquit接口,你會(huì)發(fā)現(xiàn)每次調(diào)用后,容器的重啟所需要的時(shí)間都會(huì)變長(zhǎng),POD的狀態(tài)處于NotReady或者CrashLoopBackOff狀態(tài),背后的原理如下圖所示:

《圖1.8 容器實(shí)例重啟等待策略》

如上圖所示,第一次容器異常退出后,Kubernetes會(huì)立即進(jìn)行重啟,如果容器容器由于某種原因,需要再次重啟,那么Kubernetes會(huì)等待10秒鐘再重啟容器實(shí)例,而這個(gè)等待時(shí)間會(huì)隨著后續(xù)的重啟被加倍,20s,40s,80s,160s,從160s以后,延遲時(shí)間會(huì)變成5分鐘,我們把每次重啟需要等待的時(shí)間間隔加倍這種策略叫指數(shù)級(jí)回退策略(exponential back-off)。在極端情況下,容器的確會(huì)被block高達(dá)5分鐘才能重啟。而這個(gè)指數(shù)級(jí)回退策略會(huì)在容器成功運(yùn)行10分鐘后被清零。筆者在目前負(fù)責(zé)的項(xiàng)目上,多次看到容器處于CrashLoopBackOff的狀態(tài),這是很多調(diào)試階段應(yīng)用經(jīng)常遇到的場(chǎng)景,希望大家通過(guò)這里的介紹,能夠掌握這個(gè)原理。

我們?cè)谇斑吔榻B的內(nèi)容都是Kubernetes在我們的容器異常退出的時(shí)候,幫助我們重啟容器來(lái)提供可用性,但是很多時(shí)候容器并沒(méi)有退出,但是提供的服務(wù)卻無(wú)法訪問(wèn)。舉個(gè)例子,Java應(yīng)用程序如果有內(nèi)存泄漏,會(huì)返回內(nèi)存不足的異常,但是這并不妨礙JVM進(jìn)程繼續(xù)運(yùn)行,理論上Kubernetes應(yīng)用能檢測(cè)到這樣的異常,并且重啟容器實(shí)例,但是很顯然Kubernetes把這個(gè)問(wèn)題交給了運(yùn)維和開(kāi)發(fā)人員來(lái)解決。

如果我們從程序本身來(lái)考慮處理這問(wèn)題,很明顯會(huì)是個(gè)悖論,因?yàn)閼?yīng)用自己都沒(méi)有內(nèi)存了,如何檢測(cè)自己出問(wèn)題。因此這個(gè)問(wèn)題必須要從外部來(lái)解決,而Kubernetes提供了叫Liveness probes的功能,來(lái)從外部對(duì)應(yīng)用判活。

【應(yīng)用程序的健康探測(cè)】

我們可以給POD配置liveness probe來(lái)定期的檢測(cè)服務(wù)是否正常對(duì)外提供訪問(wèn)能力,我們可以在容器級(jí)別來(lái)設(shè)定這個(gè)liveness probe,而Kubernetes要做的就是,基于配置的信息,周期性的來(lái)訪問(wèn)應(yīng)用程序,判斷應(yīng)用程序的狀態(tài)。如果應(yīng)用程序沒(méi)有影響,或者返回錯(cuò)誤信息,容器實(shí)例就會(huì)被認(rèn)為不健康,Kubernetes就會(huì)終止容器,并基于配置的重啟策略來(lái)重啟容器實(shí)例。這里需要注意的是,Liveness probe只能用在正常應(yīng)用容器實(shí)例上,不能配置在初始化容器實(shí)例上。

Kubernets為了提供對(duì)大部分應(yīng)用程序關(guān)于判活場(chǎng)景的支持,提供如下三種liveness probe機(jī)制:

- HTTP GET probe通過(guò)發(fā)送發(fā)送請(qǐng)求給我們制定的資源地址,包括IP地址和端口號(hào),如果probe收到正常的http響應(yīng)(比如2xx和3xx),那么probe就認(rèn)為應(yīng)用健康;如果probe收到除2xx和3xx之外的http狀態(tài)碼,那么就認(rèn)為此次probe探測(cè)失敗。

- TCP Socket probe通過(guò)在目標(biāo)容器制定的端口上建立TCP連接來(lái)判斷容器的狀態(tài)。如果TCP連接可以被成功的建立,那么就認(rèn)為probe成功,如果無(wú)法成功建立TCP連接,就認(rèn)為失敗。

- Exec probe通過(guò)在目標(biāo)容器實(shí)例上執(zhí)行命令來(lái)判斷容器的健康狀態(tài)。執(zhí)行如果返回非0返回碼,就認(rèn)為執(zhí)行失敗,如果命令執(zhí)行超時(shí),也會(huì)認(rèn)為probe執(zhí)行失敗。

Kubernetes除了提供這種liveness probe的機(jī)制,還提供了一種叫startup probe的機(jī)制,我們會(huì)在后續(xù)的文章中介紹這種機(jī)制。好了,今天的內(nèi)容就這么多了,筆者會(huì)在下篇文章中詳細(xì)介紹如何配置liveness probe,敬請(qǐng)期待!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容