AWS EKS中實(shí)現(xiàn)應(yīng)用平滑升級(jí),使用aws的ecs構(gòu)建的架構(gòu)圖AWS EKS中實(shí)現(xiàn)應(yīng)用平滑升級(jí)在EKS中,運(yùn)行的應(yīng)用大多都會(huì)考慮和CI/CD或者DevOps流程相集成,從而可以更快發(fā)布新版本應(yīng)用,將新產(chǎn)品功能推向市場(chǎng)。同時(shí),由于EKS自身的版本也有生命周期,所以也會(huì)面臨由于EKS升級(jí)而導(dǎo)致的應(yīng)用向新的NodeGrou......
在EKS中,運(yùn)行的應(yīng)用大多都會(huì)考慮和CI/CD或者DevOps流程相集成,從而可以更快發(fā)布新版本應(yīng)用,將新產(chǎn)品功能推向市場(chǎng)。同時(shí),由于EKS自身的版本也有生命周期,所以也會(huì)面臨由于EKS升級(jí)而導(dǎo)致的應(yīng)用向新的NodeGroup進(jìn)行的平級(jí)遷移。如何做到應(yīng)用的平滑升級(jí)是快速上線新產(chǎn)品特性的重要前提。
在EKS環(huán)境中,為了實(shí)現(xiàn)應(yīng)用的平滑升級(jí),需要滿足以下幾個(gè)方面的需求:
1.在舊的容器實(shí)例被終止前,必須要有新的實(shí)例被創(chuàng)建來為后續(xù)的新請(qǐng)求服務(wù)。這也就要求應(yīng)用能同時(shí)運(yùn)行多個(gè)容器副本,也就要求有客戶端或者服務(wù)端的負(fù)載均衡器將請(qǐng)求在新舊版本之間進(jìn)行分發(fā),同時(shí)要求容器應(yīng)用的無狀態(tài)化,有狀態(tài)的容器應(yīng)用不在此文章的討論范圍內(nèi)。
2.確保創(chuàng)建的的新版本容器能處理應(yīng)用請(qǐng)求后再加入到負(fù)載均衡器后端:在新的實(shí)例被創(chuàng)建后,必須等待完成應(yīng)用的初始化,才能被設(shè)置為可處理新請(qǐng)求的Ready狀態(tài),避免新請(qǐng)求路由到未準(zhǔn)備好的應(yīng)用容器。由于Kubernetes的Pod機(jī)制支持在Pod創(chuàng)建時(shí),可以使用Readiness探針機(jī)制來保證只有Pod中的應(yīng)用已經(jīng)就緒時(shí)才加入到前端Service的Endpoints列表中,從而可以正常接受請(qǐng)求輸入。由于此部分實(shí)現(xiàn)方式較為簡(jiǎn)單明了,所以本文不作過多討論。
3.確保舊版本容器在徹底被移除前處理完遺留請(qǐng)求:在停止容器時(shí),需要保證容器處理完當(dāng)前的請(qǐng)求才能最終退出,避免引起客戶端錯(cuò)誤。如何實(shí)現(xiàn)這個(gè)保證,將會(huì)是本文討論的重點(diǎn)。
4.需要使用特定的升級(jí)方法,如滾動(dòng)升級(jí)和藍(lán)綠部署方法。Kubernetes的Recreate方法不適用于應(yīng)用的平滑升級(jí),因?yàn)檫@種方法會(huì)有應(yīng)用上的停機(jī)時(shí)間。
本文將深入內(nèi)里討論在EKS環(huán)境中以下幾種情形下是否可以實(shí)現(xiàn)平滑升級(jí),以及如何實(shí)現(xiàn)。(EKS中Pod的IP地址是平坦模式,也就是和計(jì)算節(jié)點(diǎn)地址在同一范圍內(nèi)的物理地址,不同于采用Overlay方式時(shí)的地址模式,所以本文的部分內(nèi)容可能不適用其他Kubernetes環(huán)境)。
1.在純EKS場(chǎng)景下.Kubernetes的Service對(duì)象作為服務(wù)端負(fù)載均衡器,Deployment的滾動(dòng)部署作為升級(jí)方法。
2.在EKS中使用SpringCloud微服務(wù)框架的場(chǎng)景下.Ribbon作為客戶端負(fù)載均衡器,Deployment的滾動(dòng)部署作為升級(jí)方法。
3.在EKS中使用AWS AppMesh/Istio這種以SideCar形式實(shí)現(xiàn)微服務(wù)框架的場(chǎng)景下.Virtual Service作為客戶端負(fù)載均衡器,采用Virtual Service的藍(lán)綠部署方法。
在純EKS場(chǎng)景下實(shí)現(xiàn)平滑升級(jí)
在純屬EKS環(huán)境中,Kubernetes的Service對(duì)象作為服務(wù)端負(fù)載均衡器,Deployment的滾動(dòng)部署作為升級(jí)方法。
在升級(jí)應(yīng)用過程中停止掉舊版本的容器時(shí),需要停止向正在被停止的容器分發(fā)新請(qǐng)求,同時(shí)也要容忍容器將連接中未處理請(qǐng)求處理完成再退出,也即要滿足如下兩個(gè)條件:
條件1:在停止容器后,如果有新請(qǐng)求發(fā)國際快遞此容器,請(qǐng)求自然會(huì)得不到正確的響應(yīng),所以要求在前端的請(qǐng)求分發(fā)層面,不再將請(qǐng)求路由到將即將刪除的容器。
條件2:但同時(shí)要求針對(duì)未完成的請(qǐng)求能繼續(xù)處理,比如在客戶端和后端服務(wù)容器之間建立的連接上,仍有正在處理的請(qǐng)求或者有后續(xù)的連續(xù)請(qǐng)求,應(yīng)該要允許這些請(qǐng)求處理完成。
如何在Pod的停止過程中實(shí)現(xiàn)如上兩個(gè)條件,需要解析Pod生命周期中的停止過程。
圖一:Pod關(guān)閉流程
開始Pod的delete流程,Pod進(jìn)入關(guān)閉流程,觸發(fā)原因可能是用戶執(zhí)行Pod的delete操作,也有可能是執(zhí)行rollout操作,也有可能是減少replicaset的數(shù)量。其整個(gè)流程如下:(以下過程從第1到第4步是同時(shí)發(fā)生)。
1.Kubernetes將容器狀態(tài)置為Terminating,使用命令查看狀態(tài)時(shí)會(huì)處于Terminating狀態(tài)。
2.同時(shí)通知API Server將Pod從其所屬的Endpoints中移除,比如從使用selector歸屬的某一個(gè)Service中移除,此操作執(zhí)行過程,Service的后端列表中將移除此Pod。
3.啟動(dòng)Grace shotdown定時(shí)器(值由deployment中的terminationGracePeriodSeconds參數(shù)指定,默認(rèn)值為30)。通知Pod所屬節(jié)點(diǎn)的Kubelet開始執(zhí)行Pod的shutdown操作。
4.通知計(jì)算節(jié)點(diǎn)上的Kubelet守護(hù)進(jìn)程關(guān)閉相應(yīng)的Pod。
如果Pod定義了preStop hook,kubelet會(huì)觸發(fā)此hook腳本在Pod中的執(zhí)行.preStop Hook中執(zhí)行用戶定義過程。執(zhí)行完成后,發(fā)快遞TERM信號(hào)給Pod里面的所有容器。
如果未定義PreStop Hook,直接發(fā)快遞TERM給Pod中的所有容器。
5.執(zhí)行上述過程后,如果所有清楚過程在Grace Period周期內(nèi)結(jié)束,則Pod結(jié)束工作完成。如果Pod仍然未退出,則發(fā)快遞KILL信號(hào),強(qiáng)制結(jié)束Pod。針對(duì)有preStop Hood的Pod,在發(fā)快遞KILL信號(hào)前,有額外一次2秒的時(shí)延。
6.到此,Pod實(shí)體已經(jīng)結(jié)束,繼續(xù)將Pod從API Server記錄中清除,此時(shí),客戶端無法再檢索到Pod。
首先分析Pod被刪除時(shí),是否會(huì)從其所屬的負(fù)載均衡器中移除,從而不再接收新的請(qǐng)求。純EKS模式下,Pod采用Kubernetes Service作為負(fù)載均衡器,Kubernetes Service是依賴在計(jì)算節(jié)點(diǎn)中的Iptables服務(wù)來實(shí)現(xiàn)。以一個(gè)Service有兩個(gè)對(duì)應(yīng)的Pod為例,查看其在Iptables表中的實(shí)現(xiàn)。
1.找到Service的Cluster IP:kubectl get service phpapacheo customcolumns=ClusterIP:.spec.clusterIP
2.登錄任意節(jié)點(diǎn),查看Cluster IP對(duì)應(yīng)的Iptables條目
iptablesLt nat
Chain KUBESERVICES(2 references)
target prot opt source destination
KUBESVCBJ47X7EXARPH4MMJ tcp—0.0.0.0/0 10.100.81.62/default/phpapache:cluster IP/tcp dpt:80
Chain KUBESVCBJ47X7EXARPH4MMJ(1 references)
target prot opt source destination
KUBESEPFEFBLPTU36TWIWKJ all—0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.50000000000
KUBESEP2CJO3665RSYCSLOT all—0.0.0.0/0 0.0.0.0/0
Chain KUBESEP2CJO3665RSYCSLOT(1 references)
target prot opt source destination
KUBEMARKMASQ all—192.168.92.92 0.0.0.0/0
DNAT tcp—0.0.0.0/0 0.0.0.0/0 tcp to:192.168.92.92:80
KUBEMARKMASQ all—192.168.92.92 0.0.0.0/0
DNAT tcp—0.0.0.0/0 0.0.0.0/0 tcp to:192.168.92.92:80
每一個(gè)Cluster IP都是在Iptables的chain中,以ClusterIP為匹配條件,鏈的背后是兩個(gè)目標(biāo)KUBESEPFEFBLPTU36TWIWKJ和KUBESEP2CJO3665RSYCSLOT,分別對(duì)應(yīng)的是后端兩個(gè)Pod的地址。兩個(gè)目標(biāo)之間是利用statistic模式采用random選擇,否則流量只會(huì)路由到第一個(gè)目標(biāo)中。0.50000000表示選擇到第一個(gè)目標(biāo)的機(jī)率是50%,那選擇到第二個(gè)目標(biāo)的機(jī)率也是50%。
在刪除Pod的同時(shí),觀察在刪除Pod時(shí)iptables中的變化,watchn 0.5“iptablesLt natngrep KUBESVCDTU5BYSZICDKSKGDA 3″。在delete命令執(zhí)行后,API Server立即通知各計(jì)算節(jié)點(diǎn)上kubelet守護(hù)進(jìn)程將Iptables中對(duì)應(yīng)Pod的target刪除,此實(shí)現(xiàn)可以滿足條件一:執(zhí)行刪除Pod的命令后,不再將新請(qǐng)求發(fā)國際快遞正在被結(jié)束的Pod上。
假如請(qǐng)求是通過基于TCP,或者基于Http1.1或者Http2.0這種長(zhǎng)連接機(jī)制的協(xié)議,后續(xù)請(qǐng)求可能會(huì)共用同一連接,而將后續(xù)的請(qǐng)求發(fā)國際快遞被刪除的Pod,那Iptables實(shí)現(xiàn)的Service負(fù)載均衡是否也可以將同一連接中未完成的后續(xù)請(qǐng)求繼續(xù)發(fā)國際快遞舊版本Pod呢?這就涉及到Iptables中的兩條規(guī)范:
Chain中目標(biāo)的選擇,只在建立TCP連接的時(shí)候進(jìn)行。
IPtables的ConnCtrack機(jī)制通過Iptables建立的連接,記錄連接對(duì)應(yīng)的后端目標(biāo)。
通過規(guī)范一,可以保證針對(duì)舊版本Pod的后續(xù)的請(qǐng)求不會(huì)發(fā)國際快遞新的Pod。
通過規(guī)范二,可以保證針對(duì)舊版本Pod的后續(xù)的請(qǐng)求仍然發(fā)國際快遞正在被停止的舊版本Pod上。
也就是先前通過iptables建立的到后端的Iptables的連接,會(huì)在conntrace模塊中記錄連接信息。通過在發(fā)起節(jié)點(diǎn)上的connctrack中記錄連接信息,連接中的后續(xù)請(qǐng)求可以直接經(jīng)過同一連接到達(dá)后端。連接保持信息可以通過cat/proc/net/nfconntrack文件查看,如
ipv4 2 tcp 6 86400 ESTABLISHED src=192.168.25.253 dst=10.100.81.62 sport=34784 dport=80 src=192.168.23.104 dst=192.168.25.253 sport=80 dport=34784[ASSURED]mark=0 zone=0 use=2
也就是建立的連接信息會(huì)保持一段時(shí)間,此時(shí)間由系統(tǒng)選項(xiàng)/proc/sys/net/netfilter/nfconntracktcptimeoutestablished控制,也就是這里的86400s/24小時(shí)。也即是只要雙方不主動(dòng)關(guān)閉連接,仍然能將請(qǐng)求在后續(xù)24小時(shí)內(nèi)通過conntrack發(fā)國際快遞對(duì)應(yīng)的后端。
連接信息已經(jīng)保存,請(qǐng)求可以繼續(xù)向目標(biāo)發(fā)快遞,只要求目標(biāo)Pod繼續(xù)在這段時(shí)間內(nèi)存活就可以繼續(xù)處理后續(xù)的請(qǐng)求??梢酝ㄟ^terminationGracePeriodSeconds參數(shù)延緩對(duì)Pod的KILL信號(hào)發(fā)快遞而延長(zhǎng)Pod的存活時(shí)間,因?yàn)镵ILL信號(hào)無法被Pod捕獲,Pod中的進(jìn)程會(huì)被系統(tǒng)強(qiáng)制結(jié)束。通過此參數(shù)指定一個(gè)Pod結(jié)束的最長(zhǎng)時(shí)間,并配合如下機(jī)制:
*利用preStop hook,在這個(gè)hook中執(zhí)行例如sleep一段時(shí)間這樣的操作,目的是為了延緩進(jìn)入到下一步結(jié)束過程的處理。在hook執(zhí)行的這一段時(shí)間里,Pod可以繼續(xù)正常運(yùn)行。
preStop hook配合terminationGracePeriodSeconds就能實(shí)現(xiàn)延緩Pod結(jié)束的目的,當(dāng)然也可以考慮在TERM的信號(hào)處理函數(shù)中執(zhí)行延緩?fù)顺霾僮?,但推薦采用preStop hook的方式,因?yàn)門ERM信號(hào)處理需要在應(yīng)用代碼中進(jìn)程編程,而且需要Pod中每個(gè)容器都要進(jìn)行TERM捕獲的捕獲,復(fù)雜度較高。相反preStop hook這種方式可以直接在deployment中配置,簡(jiǎn)單明了。配置如下:
spec:
containers:
lifecycle:
preStop:
exec:
command:
/bin/sh c sleep 120
terminationGracePeriodSeconds: 120
通過以上過程的分析,通過EKS的基于Iptables的Service服務(wù)結(jié)合滾動(dòng)升級(jí)是可以實(shí)現(xiàn)平滑升級(jí)的。包含以下Service類型
*ClusterIP/NodePort/EKS中類型為L(zhǎng)oadBalancer,因?yàn)檫@些Service都是基于利用Iptables實(shí)現(xiàn)服務(wù)端負(fù)載均衡。
但不包含類型為Headless的Service,因?yàn)榇朔NService的實(shí)現(xiàn)不基于Iptables。
不過由于以上機(jī)制是kubernetes配合計(jì)算節(jié)點(diǎn)的實(shí)現(xiàn),不是純粹的應(yīng)用層機(jī)制。如果能完全在應(yīng)用層實(shí)現(xiàn)平滑升級(jí),則整個(gè)過程更優(yōu)雅。
在EKS中使用SpringCloud微服務(wù)框架的場(chǎng)景下實(shí)現(xiàn)平滑升級(jí)
在SpringCloud微服務(wù)框架下,服務(wù)組件之間采用Ribbon進(jìn)行請(qǐng)求的分發(fā)。Ribbon是客戶端負(fù)載均衡模式,后端服務(wù)節(jié)點(diǎn)的選擇由客戶端負(fù)責(zé),在Pod的delete過程中需要讓客戶端不再分發(fā)新的請(qǐng)求到正被刪除的節(jié)點(diǎn)。需要有機(jī)制讓客戶端感知到后端Pod的刪除行為,此機(jī)制同樣可以利用Pod的preStop hook加以實(shí)現(xiàn)。以客戶端應(yīng)用通過Zuul訪問后端服務(wù)為例,與在純粹的EKS環(huán)境中preStop hook單純執(zhí)行sleep操作不同的是,要在preStop hoo中執(zhí)行:
1.向Eureka Server注銷自己
2.等待客戶端Ribbon中的ServerList過期,在此時(shí)間內(nèi),Pod仍然能接收前端新的請(qǐng)求
圖二:SpringCloud中服務(wù)注銷及失效周期
由于Eureka和Zuul中組件都有各自的緩存,為了讓Pod在客戶端徹底過期,需要準(zhǔn)確計(jì)算應(yīng)該在上述第二步中的等待時(shí)間,涉及以下幾個(gè)階段。
Pod向Eurora注銷自己,清除readwritemap中的記錄,同時(shí)停止發(fā)快遞心跳防止再注冊(cè)
Eureka的readwritemap map同步信息到readonlymap的間隔時(shí)間eureka.server.responsecacheupdateintervalms(30s)
Zuul作為Eureka客戶端,每隔eureka.client.registryfetchintervalseconds(30)去從readonly map中去拉取服務(wù)列表
Ribbon更新其ServerList的時(shí)間間隔ribbon.ServerListRefreshInterva(30)
Pod在結(jié)束前主動(dòng)向Eureka發(fā)起注銷操作,則要保證在preStop hook中等待30+30+30=90秒的時(shí)間,客戶端才不會(huì)再使用此服務(wù)的endpoint建立新的請(qǐng)求連接,所以此時(shí)的terminationGracePeriodSeconds值應(yīng)該設(shè)置為90。如果此連接是長(zhǎng)連接,則還要加上在此連接中處理所有請(qǐng)求需要的時(shí)間作為最終的等待時(shí)間,比如5s,那在hoop中等待的時(shí)間應(yīng)該是95s。
在內(nèi)部服務(wù)之間使用Feign進(jìn)行訪問,F(xiàn)eign內(nèi)部也依賴Ribbon進(jìn)行負(fù)載均衡,所以整個(gè)實(shí)現(xiàn)方法和Zuul場(chǎng)景下并無不同。
在EKS環(huán)境中使用AppMesh/Istio Sidecar形式的微服務(wù)框架實(shí)現(xiàn)平滑升級(jí)
AppMesh是AWS推出的基于sidecar的微服務(wù)框架,基于每個(gè)集成到每個(gè)Pod的Sidecar,可以實(shí)現(xiàn)請(qǐng)求在多個(gè)版本的Pod之間分發(fā),從而可以以藍(lán)綠部署的方式實(shí)現(xiàn)Pod級(jí)的應(yīng)用平滑升級(jí)。
AppMesh在標(biāo)準(zhǔn)的Kubernetes的Service和Pod基礎(chǔ)之上,將一個(gè)Service抽象成如下架構(gòu)。
圖三:AppMesh架構(gòu)圖
AppMesh中各組件定義如下:
由于Virtual Router里面在定義路由規(guī)則時(shí),可以將請(qǐng)求分別定向到不同版本的后端,并且可以在不同后端之間通過調(diào)整Weight值進(jìn)行按比例分配,所以就為應(yīng)用提供了藍(lán)綠部署的能力。通過藍(lán)綠部署,應(yīng)用可以以最小的代價(jià)實(shí)現(xiàn)平滑升級(jí),不需要考慮Kubernes在停止Pod時(shí)的具體實(shí)現(xiàn),也不需要考慮SpringCloud中服務(wù)向客戶端注銷的問題。
在AppMesh中升級(jí)后端應(yīng)用時(shí),只需要使用新的Deployment和Service創(chuàng)建新的后端,并建立新的Virtual Node,將在Virtual Rotuer中將部分流量分配到新的Virtual Node。在確認(rèn)新版本應(yīng)用組件工作正常后,在Virtual Router中只需要將舊版本應(yīng)用組件對(duì)應(yīng)的Virtual Node的Weight設(shè)置為0,新版本應(yīng)用組件對(duì)應(yīng)的Virtual Node的Weight權(quán)重設(shè)置為100即可,這樣可以滿足條件1:新進(jìn)入的請(qǐng)求不會(huì)到達(dá)舊版本的Pod,而是全部發(fā)快遞給新版本的Pod。那是否能滿足條件2:舊版本的請(qǐng)求仍然發(fā)國際快遞與舊版本建立的連接中呢 ?為了進(jìn)一步說明AppMesh及Istio場(chǎng)景下,在進(jìn)行藍(lán)綠部署時(shí),是否能保證舊版本的服務(wù)仍能提供連接供未完成的請(qǐng)求執(zhí)行,特對(duì)Envoy的請(qǐng)求連接作進(jìn)一步說明。以AWS的EKS Workshop中AppMesh集成實(shí)驗(yàn)為例:https://www.eksworkshop.com/advanced/310servicemeshwithistio/。
圖四:AppMesh客戶端與服務(wù)請(qǐng)求服務(wù)流程圖
1.客戶端中的envoy會(huì)和服務(wù)端中的每條路由條目的對(duì)象所屬的envoy建立一個(gè)長(zhǎng)鏈接。
2.客戶端發(fā)起對(duì)服務(wù)端的訪問時(shí),實(shí)際上的新建一個(gè)向與自身同Pod的Envoy Proxy建立連接發(fā)起請(qǐng)求。
3.Envoy Proxy通過長(zhǎng)連接發(fā)快遞請(qǐng)求到服務(wù)端Pod的Envoy Proxy
4.服務(wù)端的Envoy Proxy再建立一個(gè)臨時(shí)連接發(fā)快遞請(qǐng)求到同Pod的應(yīng)用容器,從而完成整個(gè)請(qǐng)求鏈。
5.在設(shè)置到舊版本服務(wù)的Weight為0時(shí),Envoy Proxy到服務(wù)端之間的長(zhǎng)鏈接仍然保持,而且是永久保持,除非一端主動(dòng)發(fā)起關(guān)閉。所以不轉(zhuǎn)發(fā)新的請(qǐng)求到舊版本服務(wù)端,但仍然可以繼續(xù)處理完未完成的舊請(qǐng)求。
綜上雖然舊版本的Weight權(quán)限設(shè)置為0,但客戶端和其建立的連接卻會(huì)繼續(xù)保持,所以可以平滑切換,不會(huì)造成業(yè)務(wù)中斷。
針對(duì)Istio,里面同樣有Virtual Service的抽象,且數(shù)據(jù)層面采用的是與AppMesh一致的Envoy Proxy,以Istio自然而然也支持通過藍(lán)綠部署的方式實(shí)現(xiàn)平滑升級(jí)。
總結(jié)
通過在不同的使用模式下,有不同的方法來實(shí)現(xiàn)應(yīng)用的平滑升級(jí)
1.純Kubernetes模式下:可以實(shí)現(xiàn)滾動(dòng)模式的平滑升級(jí),需要借助Service的負(fù)載均衡能力,以及Pod的preStop能力,讓Pod在退出前能繼續(xù)處理未完成的請(qǐng)求,在preStop中的等待時(shí)間取決于客戶端一次完整請(qǐng)求的持續(xù)時(shí)間。
2.SpringCloud微服務(wù)框架下:此時(shí)Pod的preStop hook中,還需要執(zhí)行向服務(wù)注冊(cè)服務(wù)如Eureka注銷自己,并等待客戶端中Ribbon記錄過期從而不同轉(zhuǎn)發(fā)新請(qǐng)求到正在被停止的Pod,同樣地,其等待時(shí)間還需要加上客戶端一次完整請(qǐng)求的持續(xù)時(shí)間。所以preStop中hook的等待時(shí)間為客戶端記錄過期時(shí)間加上一次完整請(qǐng)求需要的處理時(shí)間。
3.在AppMesh及Istio微服務(wù)框架下:每個(gè)Pod的SideCar中包含了完整的Virtual Service及請(qǐng)求Weight分配能力實(shí)現(xiàn)藍(lán)綠部署,所以可以做到對(duì)每個(gè)Pod的平滑升級(jí),而且不需要針對(duì)Pod配置preStop,這種模式下實(shí)現(xiàn)應(yīng)用服務(wù)的平滑升級(jí)最為方便。
特別聲明:以上文章內(nèi)容僅代表作者本人觀點(diǎn),不代表ESG跨境電商觀點(diǎn)或立場(chǎng)。如有關(guān)于作品內(nèi)容、版權(quán)或其它問題請(qǐng)于作品發(fā)表后的30日內(nèi)與ESG跨境電商聯(lián)系。
二維碼加載中...
使用微信掃一掃登錄
使用賬號(hào)密碼登錄
平臺(tái)顧問
微信掃一掃
馬上聯(lián)系在線顧問
小程序
ESG跨境小程序
手機(jī)入駐更便捷
返回頂部