<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          混沌工程推動(dòng)可觀測(cè)性的最佳實(shí)踐 | IDCF

          共 6088字,需瀏覽 13分鐘

           ·

          2021-08-31 09:24

          3173f667b20eb12d9264794ff0a23cb8.webp

          來(lái)源:混沌工程實(shí)踐?作者:水球潘/JohnnyPan

          本文中,我們使用 DDD 領(lǐng)域建模的方法,設(shè)計(jì)了一個(gè)合理且復(fù)雜的微服務(wù)叫車系統(tǒng),并在該系統(tǒng)之上,利用OpenTelemetry進(jìn)行了可觀測(cè)性構(gòu)造實(shí)踐,最后采用“強(qiáng)化混沌工程”的方法論,借助故障注入手段,實(shí)現(xiàn)了用于驗(yàn)證可觀測(cè)性價(jià)值的最佳實(shí)踐,并將游戲沖關(guān)的玩法融入軟件開(kāi)發(fā)的生命周期中,提升應(yīng)用的排障能力,以此降低系統(tǒng)的MTTR。


          一、一個(gè)類 Uber 叫車試點(diǎn)應(yīng)用

          ccf37e296e40b1fd1539997fe1c1af9b.webp



          為了達(dá)到應(yīng)用可觀測(cè)性構(gòu)造的效果,需要設(shè)計(jì)一個(gè)足夠復(fù)雜的試點(diǎn)應(yīng)用,這里我選擇了這個(gè)類 Uber App 的設(shè)計(jì)和開(kāi)發(fā)。

          1.1 領(lǐng)域建模

          首先,通過(guò)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)DDD的事件風(fēng)暴,集眾人之力完成該試點(diǎn)應(yīng)用的領(lǐng)域模型設(shè)計(jì):

          e845a3b4aa61d9bf9e1392bf4ae6e56e.webp

          隨后觀察領(lǐng)域中重復(fù)出現(xiàn)的名詞,找出限界上下文:

          d9ae31f5a9a57935a05d1de8a408828d.webp

          這樣,類 Uber 叫車試點(diǎn)應(yīng)用的領(lǐng)域模型建模完畢。

          1.2 微服務(wù)架構(gòu)設(shè)計(jì)

          接著,我們繼續(xù)設(shè)計(jì)試點(diǎn)應(yīng)用微服務(wù)化的系統(tǒng)架構(gòu):

          171e4ed1c41d8e817574e15f6674cba3.webp

          類 Uber 叫車試點(diǎn)應(yīng)用有五個(gè)微服務(wù),其中四個(gè)是由限界上下文衍生出來(lái)的,包括:

          • User 乘客?
          • Match 叫車匹配
          • Trip 載客旅程
          • Payment 支付金流
          另外加開(kāi)了broker-service,作為Websocket的統(tǒng)一出口。微服務(wù)之間的通信機(jī)制,除了最直接的RESTFul API,即上下游調(diào)用關(guān)系之外,也使用了RabbitMQ作為消息隊(duì)列將領(lǐng)域事件有效廣播到各服務(wù)去。到了這邊,類 Uber 叫車試點(diǎn)應(yīng)用的設(shè)計(jì)也已趨于完整,接下來(lái)就是實(shí)現(xiàn)。1.3 Java Spring Boot 實(shí)現(xiàn)使用 Java Spring Boot,遵循 Clean Architecture,最后花了一個(gè)月的時(shí)間將后端完整開(kāi)發(fā)出來(lái),共計(jì)大約 7000 行代碼。本文并不會(huì)深入展開(kāi) Clean Architecture 的開(kāi)發(fā)過(guò)程,有興趣的朋友可參考:https://github.com/Johnny850807/Waber

          7e6b5a49ea308936e5b441fd75a03b88.webp


          二、OpenTelemetry 構(gòu)造可觀測(cè)性

          ccf37e296e40b1fd1539997fe1c1af9b.webp



          2.1 可觀測(cè)性概念的回顧
          平常在開(kāi)發(fā)環(huán)境中,遇到Bug的時(shí)候?yàn)榍蠓奖?,都?huì)開(kāi)啟調(diào)試器,借助中斷來(lái)確認(rèn)應(yīng)用的行為是否符合預(yù)期,但是,一旦應(yīng)用部署到各環(huán)境中,便不能再使用調(diào)試器來(lái)觀察應(yīng)用的行為了。
          所謂的可觀測(cè)性,指的是應(yīng)用在被部署到某個(gè)線上環(huán)境后,其本身還能透過(guò)“某種機(jī)制”來(lái)讓開(kāi)發(fā)人員,便捷地觀測(cè)應(yīng)用本身在線上“各個(gè)時(shí)間點(diǎn)”的行為。

          28913a162ce240d347c6838970500134.webp

          其中,Trace記錄著每個(gè)功能完整的執(zhí)行過(guò)程。由于執(zhí)行過(guò)程會(huì)充滿著「上下游的調(diào)用關(guān)系」,結(jié)合起來(lái)就形成了一棵樹(shù)。

          88862c90d4a5278772d19f762d48d625.webp

          那么,在技術(shù)上到底該如何呈現(xiàn)?手動(dòng)分析:

          c7cf8fce1237ff66cc08ac4577689cf4.webp

          Grafana 呈現(xiàn):

          1d264f16bd3d84d0519d777d0df06834.webp

          2.2 OpenTelemetry 的新嘗試
          OpenTelemetry的發(fā)明并非要解決新的問(wèn)題,而是一個(gè)在可觀測(cè)性三大支柱的需求下,實(shí)現(xiàn)單一標(biāo)準(zhǔn)的框架。
          我們決定要嘗試OpenTelemetry這一項(xiàng)CNCF近期推出的技術(shù)框架。我們先來(lái)看一下在以前,我們都是如何做到分布式鏈路追蹤的?OpenTracing + Jaeger:

          fdbaabe414460b94ad5348fb425834a8.webp

          以O(shè)penTelemetry的設(shè)計(jì)觀點(diǎn)來(lái)看,這種“大同小異”便有了可以實(shí)施統(tǒng)一標(biāo)準(zhǔn)的可行性。

          2873d02563e753869021356fb7be7b6e.webp

          在應(yīng)用代碼中,使用OpenTelemetry SDK來(lái)進(jìn)行數(shù)據(jù)采集,下一步Exporter會(huì)將采集到的數(shù)據(jù)如:log/trace/metrics,用配置好的格式傳遞到Agent去,Agent只是作為數(shù)據(jù)傳遞的中介,最后會(huì)再提供給Collector。
          為了要在應(yīng)用中做分布式鏈路追蹤,以往我們都必須直接依賴廠商鏈路追蹤產(chǎn)品的SDK來(lái)去埋點(diǎn),但如此一來(lái),未來(lái)如果決定要更換廠商時(shí),則應(yīng)用會(huì)有很大一部分需要重寫。OpenTelemetry扮演的就正是那一層抽象的標(biāo)準(zhǔn)接口,如果應(yīng)用依賴的是OpenTelemetry的API,那未來(lái)要更換廠商時(shí),就只需要改變OpenTelemetry的配置就行,任何一行代碼都不需要重寫。2.3 Java Agent 的自動(dòng)采集首先要先到Github下載OpenTelemetry最新釋出的Java Agent JAR。https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases接下來(lái)只需要將這個(gè)JAR,在Dockerfile中將其COPY到鏡像中,然后在CMD中有關(guān)Java的執(zhí)行指令中,添加javaagent參數(shù),將其指向JAR的位置,一并執(zhí)行就行。

          42495e5055ab3c7a5edd9791a5036948.webp

          此外,還需要把追蹤系統(tǒng)一并部署起來(lái),例如:Jaeger 或 Grafana Tempo。基本范例可參考:https://github.com/Johnny850807/Spring-Boot-Demo-Observability-with-Open-Telemetry一行代碼都不用寫,就能夠產(chǎn)生出以下的 Trace 了:

          c5ae91ddde5cdc634eb3599db365c039.webp

          以Java Agent方式執(zhí)行的OpenTelemetry,開(kāi)發(fā)人員不再需要親自埋點(diǎn),不需要對(duì)鏈路追蹤系統(tǒng)有任何的認(rèn)知,更不需要為此寫任何一行代碼。OpenTelemetry會(huì)尋找所有被標(biāo)注上 @RestController 的物件,并在其每個(gè)RESTFul方法調(diào)用之前,主動(dòng)進(jìn)行數(shù)據(jù)采集,幫忙埋下適當(dāng)?shù)?Trace。2.4 定制化采集在體驗(yàn)到自動(dòng)采集的強(qiáng)大之后,大家心中浮現(xiàn)的第一個(gè)問(wèn)題肯定是如果我要定制化采集的話,要怎么辦?
          • 第一種方法:由環(huán)境變量去設(shè)置 include / exclude 的類別或方法。?
          • 第二種方法:使用 @WithSpan 來(lái)增加想要被放進(jìn) Trace 的方法。
          以類 Uber 試點(diǎn)應(yīng)用為例,有兩個(gè) POJO 物件:StartDriving 和 FindCurrentTrip ,也想要被加入到 Trace 之中。

          430fc814c96720887cf194e5fb39dfdd.webp

          由于這兩個(gè)物件只是單純的 POJO,因此在預(yù)設(shè)的情況并不會(huì)被OpenTelemetry捕獲,為了將其加入到 Trace 之中,只需要其方法上加上 @WithSpan,就能夠得到以下的結(jié)果:

          9c3ee96c0818dca64a269b0096ebe8bc.webp

          我們借助強(qiáng)大的OpenTelemetry in Java,很方便地實(shí)現(xiàn)了類 Uber 試點(diǎn)應(yīng)用的可觀測(cè)性構(gòu)造。但是,以 SRE 的觀點(diǎn)來(lái)看:
          一直以來(lái),SRE 都希望能夠最小化可觀測(cè)性構(gòu)造實(shí)踐,以減少其所帶來(lái)的額外維護(hù)成本。
          因此,我們需要一種新的最佳實(shí)踐,來(lái)證明可觀測(cè)性構(gòu)造的價(jià)值。

          三、推動(dòng)可觀測(cè)性的最佳實(shí)踐

          ccf37e296e40b1fd1539997fe1c1af9b.webp



          83a7f32cf3964174d67072ee9fc625ef.webp經(jīng)過(guò)了思考與設(shè)計(jì),最后我參考了混沌工程的精神,開(kāi)發(fā)出一個(gè)方法,可以告訴我“目前的可觀測(cè)性實(shí)踐是否仍有缺陷”?我把這個(gè)方法稱之為“強(qiáng)化混沌工程”。3.1 叫車流量的自動(dòng)化模擬但是,目前僅有一個(gè)類 Uber 的叫車系統(tǒng),還需要模擬乘客和司機(jī)的行為,來(lái)自動(dòng)化地產(chǎn)生合理的叫車流量。44aa0ed2d7f6c89363213b789a079174.webp如上所附的動(dòng)畫(huà)所示,在經(jīng)過(guò)數(shù)日的開(kāi)發(fā)后,終于做出了一個(gè)簡(jiǎn)單的自動(dòng)化叫車流量產(chǎn)生系統(tǒng)。動(dòng)畫(huà)中呈現(xiàn)的是五個(gè)司機(jī)和一個(gè)乘客的情況模擬。
          序號(hào)描述
          1乘客進(jìn)行叫車匹配
          2系統(tǒng)完成匹配,并且匹配到了某一司機(jī)
          3該名司機(jī)開(kāi)始此次載客服務(wù),并且朝著乘客的上車地點(diǎn)開(kāi)車,移動(dòng)的過(guò)程中不斷地向伺服器更新自身座標(biāo)
          4乘客進(jìn)行叫車匹配
          5系統(tǒng)完成匹配,并且匹配到了某一司機(jī)
          6該名司機(jī)開(kāi)始此次服務(wù),并朝著乘客的上車地點(diǎn)開(kāi)車,移動(dòng)的過(guò)程中不斷地向服務(wù)端更新自身座標(biāo)
          7乘客不斷接收到司機(jī)最新的座標(biāo)
          8司機(jī)抵達(dá)乘客的上車地點(diǎn),確認(rèn)乘客上車后,司機(jī)將狀態(tài)調(diào)整成“已上車”
          9司機(jī)開(kāi)車前往目的地,移動(dòng)的過(guò)程中不斷地向服務(wù)端更新自身座標(biāo)
          10司機(jī)抵達(dá)乘客欲前往的目的地,結(jié)束了服務(wù)
          11叫車流程結(jié)束,乘客將自己的座標(biāo)更新到了隨機(jī)的位置并開(kāi)始了下一次的叫車匹配

          看似簡(jiǎn)單的叫車流程,其背后的工作其實(shí)是必須由五個(gè)微服務(wù)以及 RabbitMQ 來(lái)協(xié)作完成的,現(xiàn)在我們已經(jīng)能夠創(chuàng)造大量且合理的叫車流量,總算可以來(lái)開(kāi)始實(shí)踐“強(qiáng)化混沌工程”了。

          3.2 強(qiáng)化混沌工程

          從 SRE 的視角上,可觀測(cè)性構(gòu)造的價(jià)值在于,我們要花多少時(shí)間才能夠察覺(jué)并修復(fù)好生產(chǎn)系統(tǒng)發(fā)現(xiàn)的問(wèn)題,即 MTTR。

          現(xiàn)在已經(jīng)有了“自動(dòng)化叫車流量生成器”,也有評(píng)判可觀測(cè)性構(gòu)造的標(biāo)準(zhǔn)MTTR,那剩下我最缺的就是“故障”了。

          微服務(wù)等分布式系統(tǒng)在開(kāi)發(fā)和運(yùn)維上帶來(lái)更高的門檻和復(fù)雜度,因此混沌工程便也開(kāi)始被不斷提倡。講白一點(diǎn),混沌工程就是“有目的地對(duì)待測(cè)系統(tǒng)搞破壞來(lái)提早揭露系統(tǒng)的問(wèn)題”。

          為了揭露系統(tǒng)的問(wèn)題,我們需要先對(duì)待測(cè)系統(tǒng)定義其穩(wěn)態(tài)。以類 Uber 叫車試點(diǎn)應(yīng)用來(lái)說(shuō),穩(wěn)態(tài)便是“能夠完整且順暢地執(zhí)行每一個(gè)叫車流程”。

          借鑒了混沌工程的思維,將其運(yùn)用到可觀測(cè)性場(chǎng)景中的話,則是“要有目的地在系統(tǒng)中搞破壞來(lái)提早揭露可觀測(cè)性構(gòu)造的缺陷”。

          對(duì)此,由于使用場(chǎng)景較為特殊,是否有現(xiàn)存的第三方工具,能夠滿足類 Uber 叫車試點(diǎn)應(yīng)用定制化的混沌場(chǎng)景,我并沒(méi)有太大的自信,因此我決定自己實(shí)作一個(gè)能夠?qū)崿F(xiàn)混沌工程的技術(shù)架構(gòu),以下圖所示:

          b6b30cae0a292422ef13594a2d0c3dc8.webp

          新開(kāi)了一個(gè)服務(wù)在圖正中央,稱之為Chaos-Server。

          而在應(yīng)用各個(gè)服務(wù)中都會(huì)執(zhí)行一個(gè)Chaos Client ,即Chaos Agent。

          Chaos-Server和各個(gè)Chaos-Client互相溝通,傳遞指令,來(lái)實(shí)現(xiàn)整個(gè)混沌工程的流程。

          我們只需要在Chaos的操作頁(yè)上對(duì)Chaos-Server下達(dá)命令就好。

          3.3 沖關(guān)游戲的基本玩法

          我們把混沌工程的流程設(shè)計(jì)成了一款沖關(guān)游戲,下面是基本的玩法:

          序號(hào)步驟描述
          1部署好Chaos-Server和Chaos-Client,啟動(dòng)自動(dòng)化叫車流量生成器。
          2瀏覽Chaos操作頁(yè) /api/chaos ,這個(gè)頁(yè)面可以用來(lái)對(duì)Chaos-Server下達(dá)指令。
          3開(kāi)始一個(gè)新的關(guān)卡:每一個(gè)關(guān)卡都可以由一串隨意的字串來(lái)產(chǎn)生。
          4調(diào)用 API,如/api/chaos/fun/56a8d709-9c22-489e-b44e-6d86f81796b2,其中的隨意字串就是新關(guān)卡的唯一標(biāo)識(shí)。
          5一群特定未知的Chaos就被埋好在應(yīng)用的各個(gè)服務(wù)中了。
          6接下來(lái)繼續(xù)在Chaos操作頁(yè)上進(jìn)行,操作頁(yè)上會(huì)顯示所有候選的Chaos名單,并且還會(huì)顯示在這些Chaos中,有幾個(gè)真的被激活了。
          7沖關(guān)開(kāi)始后,自動(dòng)化叫車流量生成器便會(huì)進(jìn)入到“不穩(wěn)定”的狀態(tài),由于Chaos的緣故,叫車流程將會(huì)受到影響而被阻斷。因此我們可以開(kāi)始進(jìn)行排障了。
          8在排障過(guò)程中,利用可觀測(cè)性構(gòu)造去觀察應(yīng)用行為,看看能否在最短的時(shí)間內(nèi)找出問(wèn)題來(lái)。
          9一旦找到任何潛在的問(wèn)題,對(duì)照Chaos操作頁(yè)上的名字,選擇最可疑的那一個(gè)將其殺掉。
          10由于Chaos的名字直接以破壞的內(nèi)容進(jìn)行命名,因此我們能根據(jù)名字來(lái)去進(jìn)行揣測(cè)。
          11如果殺掉成功,則Chaos的數(shù)量會(huì)少一個(gè)。反之,則會(huì)顯示訊息:”You are mis-killing trip.SaveTripDelay, he is not the mole!”。
          12反反覆覆地游玩,直到將所有Chaos趕盡殺絕為止。
          13如果最后發(fā)現(xiàn)無(wú)法通關(guān),則代表可觀測(cè)性不夠完整,此時(shí)應(yīng)該要記下筆記,對(duì)原先的穩(wěn)態(tài)假說(shuō)進(jìn)行修正,并再次進(jìn)行同一個(gè)關(guān)卡。

          3.4 體驗(yàn)一次真正的沖關(guān)游戲

          首先到Grafana的儀表盤上,可以看到三個(gè)基本面板:上方為 Metrics,包括:左上為即時(shí)的叫車匹配數(shù)量,右上為應(yīng)用中的錯(cuò)誤數(shù)量;而下方則顯示即時(shí)的 Logs。

          a3dc2a9d4f9e3c701b548ff0b86809a3.webp

          開(kāi)始一個(gè)新的關(guān)卡 56a8d709-9c22-489e-b44e-6d86f81796b2:

          1c3b7df8fb5174d3605f38a0ae678c2d.webp

          發(fā)現(xiàn)叫車流程整個(gè)卡住了,看來(lái),Chaos是真的開(kāi)始在搞破壞了…。

          e525b90ebc15ec422a7b1e6cf72746d1.webp

          一段時(shí)間之后,便在Slack頻道上收到了一個(gè)錯(cuò)誤告警,告訴我們是時(shí)候去排障了。

          cf463084fd2f9ab76bd672c04c450b67.webp

          同時(shí)也在面板上發(fā)現(xiàn)上方叫車匹配的數(shù)量急劇下滑,而下方也開(kāi)始產(chǎn)生出了錯(cuò)誤日志:

          34e3eca63e846b760db841afd74e22a2.webp

          搜索帶 “ERROR” 的日志,使用Loki的查詢語(yǔ)法:

          194e46b3ef095fc6e4ea705f121c0635.webp

          點(diǎn)擊其中一個(gè)錯(cuò)誤日志,并且從日志的 traceID 欄,直接開(kāi)啟右半部 Jaeger 的 trace 頁(yè)面。

          cf244069811d66394ab1dc982426f9fb.webp

          直接從 trace 上觀察,可以看到整個(gè)微服務(wù)的上半部是順利的,但到下方調(diào)用 /api/drivers/{driverId} 這個(gè) API 時(shí)發(fā)生了錯(cuò)誤。

          同時(shí),觀察這個(gè) trace 的執(zhí)行時(shí)間,竟然高達(dá)了12秒!發(fā)現(xiàn)瓶頸為 /api/users/{userId} 這個(gè)API。

          d981cf48dc2b53f86200c5812f3163a3.webp

          點(diǎn)擊 span 可以看到更多詳細(xì)信息,而從 DriverController.setDriverStatus 這個(gè)方法的 span 中可以看見(jiàn) exception.message 明目張膽地告訴你,它就是Chaos!

          f21361033a55bea07b119a52dd5fc526.webp

          回到操作頁(yè)面上,把對(duì)應(yīng)到的兩個(gè)Chaos殺掉:

          • user.SetDriverStatusAPIBlocked
          • user.FindUserDelay
          殺掉完兩個(gè)壞蛋之后,從頁(yè)面上可以看到接下來(lái)只剩下2個(gè)Chaos。

          da7ecf7f1415f00bc0f5fa001bdecc85.webp

          從這之后回去看叫車流程,會(huì)發(fā)現(xiàn)錯(cuò)誤似乎已經(jīng)消失,但是整個(gè)叫車流程還是卡到不行。因此接下來(lái)必須要來(lái)做性能調(diào)優(yōu)了。這一步,到 Jaeger 這個(gè) data source 上去操作,搜尋在 match service 中近期的 traces,并且使用執(zhí)行時(shí)間來(lái)從大到小排序。從搜尋結(jié)果上來(lái)看,會(huì)發(fā)現(xiàn)一個(gè)明顯的效能瓶頸,竟然有一堆 trace 的總執(zhí)行時(shí)間都高達(dá)六秒!點(diǎn)擊左側(cè)的 traceId,來(lái)導(dǎo)覽到 trace 頁(yè)面。

          cf56ac396d4464f25d6df3f853e7e226.webp

          注意到 FindAvailableDrivers 有性能瓶頸,執(zhí)行時(shí)間高達(dá)6秒,這絕對(duì)是Chaos搞的鬼。到Chaos操作頁(yè)上將其殺掉!

          d1a46a264d408b597ac51a50ccf0f7b3.webp

          接下來(lái)我們就只剩下最后一個(gè)Chaos了。此時(shí)看叫車流程,性能感覺(jué)好了一些,但是整個(gè)進(jìn)展還是很緩慢,重復(fù)使用相同的手法來(lái)排序 traces ,但這一次我們針對(duì) user service。從這一次的搜尋結(jié)果中,我們便注意到了有許多的 traces,竟然都花了長(zhǎng)達(dá)3秒的時(shí)間。

          261068fc4f7b77199f9f6ef1d90b7a2b.webp

          發(fā)現(xiàn)性能瓶頸可能在 UpdateLatestLocation ,乘客或司機(jī)在旅程行駛中,會(huì)不斷地調(diào)用它來(lái)更新自身的座標(biāo)。如果Chaos對(duì)這個(gè)調(diào)用添加了延遲,也難怪叫車流程會(huì)如此緩慢了。最后,我們就不必留情地把最后一個(gè)Chaos給殺掉。

          1905255a8eff9b331f6eec0fdd1e75aa.webp

          游戲結(jié)束后,回去看叫車流程,會(huì)發(fā)現(xiàn)整個(gè)叫車流程的順暢度又回來(lái)了!

          四、結(jié)束語(yǔ)

          ccf37e296e40b1fd1539997fe1c1af9b.webp



          83a7f32cf3964174d67072ee9fc625ef.webp本文中,我們使用 DDD 領(lǐng)域建模的方法,設(shè)計(jì)了一個(gè)合理且復(fù)雜的微服務(wù)叫車系統(tǒng),并在該系統(tǒng)之上,利用OpenTelemetry進(jìn)行了可觀測(cè)性構(gòu)造實(shí)踐,最后采用“強(qiáng)化混沌工程”的方法論,借助提早揭露實(shí)踐瑕疵的故障注入手段,實(shí)現(xiàn)了用于驗(yàn)證可觀測(cè)性構(gòu)造價(jià)值的最佳實(shí)踐方式,以游戲沖關(guān)的玩法融入軟件開(kāi)發(fā)的生命周期中,提升我們對(duì)應(yīng)用的排障能力,以此降低可觀測(cè)性構(gòu)造的MTTR。897f71c533f36c2ee20d5b8470658134.webpIDCF DevOps黑客馬拉松,獨(dú)創(chuàng)端到端DevOps體驗(yàn),精益創(chuàng)業(yè)+敏捷開(kāi)發(fā)+DevOps流水線的完美結(jié)合,2021年僅有的3場(chǎng)公開(kāi)課,數(shù)千人參與并一致五星推薦的金牌訓(xùn)練營(yíng),追求卓越的你一定不能錯(cuò)過(guò)!9月11-12日,上海站,企業(yè)組隊(duì)參賽&個(gè)人參賽均可,一年等一回,錯(cuò)過(guò)等一年,趕緊上車~??4246d900d024a4f1d507e416368a95cc.webp


          瀏覽 183
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉在线观看国产 | 国产操熟女 | 91成人影音| 成人免费视频 网站 | 久久精品夜色噜噜亚洲A∨_ |