生物多樣性提高100倍 生長快速的迷你森林風靡歐洲

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

“節氣門”聽得多 “氣門”又是干什麼的?

而上述的16V發動機則是相對於之前8v發動機而言,一台8V的四缸發動機意味着每個氣缸兩個氣門,一個進氣門和一個排氣門,如果你現在在紙上畫一個圓,然後再從圓裏面取等大的圓,結果肯定是取的等大的圓越多,取出來總面積越大。

就在汽車越來越同質化的現在,連尾標也不如以前豐富,美國車競爭最激烈的時候,車內有一個CD機都要在尾標炫耀,而車主在路上碰到同款也會下意識看下他的尾標,是不是有帶電動車窗,現在除了花樣頗多的動力表示,僅僅只有各自的看家技術會單獨掛出來。

而國內汽車的尾標,說到當年愛麗舍上的16V尾標有多少朋友會有印象呢?

要是你當時還是個熊孩子可能會以為是一道沒出完的數學題。

要是你那會剛剛上了物理課,可能會以為這是代表着16V的電源。

其實這裏的V是氣門(valve)的簡稱。

我們都知道四衝程發動機的做工過程分別是吸氣,壓縮,做功,排氣。

從中也可以看出氣門的重要性,如果簡單把發動機比作人的話,那麼進氣量和排氣量就等同於我們人的肺活量,肺活量越高肯定運動能力更強。

像下面的入門玩家。

中等玩家。

高級玩家。

而上述的16V發動機則是相對於之前8v發動機而言,一台8V的四缸發動機意味着每個氣缸兩個氣門,一個進氣門和一個排氣門,如果你現在在紙上畫一個圓,然後再從圓裏面取等大的圓,結果肯定是取的等大的圓越多,取出來總面積越大。

所以單缸四氣門的“肺活量”肯定大於兩個氣門的發動機。

那照這樣說氣門越多越好咯,理論上是這樣,所以 以前還有單缸五個氣門的發動機。像法拉利的355。

其型號就表明了該身份,3.5L排量的發動機,每個氣缸5氣門。

而更接近生活的還有國內就有的捷達王20V(氣門為3進兩出)。

當時所有開過的人對這輛車的印象就是兩個字,好開。畢竟多了一肛。

說得那麼好為什麼現在基本上都是每個氣缸4氣門呢,捷達王的20V那麼好開為什麼沒有繼續發展呢?

我們下期再來聊發動機另外一個關鍵的東西,“凸輪軸“。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

奔馳將推史上最強E級,AMG E63 Coupe橫空出世!

新E級CoupeC級CoupeS級Coupe內飾延續了E級轎車的整體布局,不過在風格上則更為年輕,運動,如碳纖維中控飾板,渦輪式出風口,無邊框車門以及全新設計的運動座椅。值得一提的是,先前透露的新E級Coupe 220d,200,300,400 4MATIC只是首發版本,而最令人期待的,則是這一代E級Coupe將確定推出AMG E63版本。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

※網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

這個配置能“防出軌” 15萬選這些家用車最放心

車身尺寸為4675*1780*1500mm,軸距達到了2710mm,乘坐空間上是比較寬敞的,加上柔軟的座椅,舒適性表現出色。動力方面,提供了1。2T和1。6T兩款發動機可選,匹配5擋手動或者6擋手自一體變速器,沉穩線性的油門調校讓動力非常容易掌控,動力總體表現出色。

隨着上個月的“超級丹”出軌事件后

辛辛苦苦塑造的男神形象毀於一旦

當然,更重要的是有媳婦的人被看得更緊了

上街口買串魚蛋都要向老婆申請

當然這隻是調侃一下

畢竟出軌並不是什麼好事情

但是隨着科技的發展

一種叫“車道偏離預警系統”的東西出現了

裝配在汽車上能有效提升行駛安全性

其工作原理是這樣的:在駕駛員未打轉向燈的情況下,電腦檢測到車輛偏離原車道之後,發出警報,以方向盤震動、發出警示音提醒等多種途徑提醒駕駛員,讓駕駛員及時糾正放向,有效減小事故的發生,哪些車上都有這種配置了,以下幾款15萬左右的合資車就有了。

北京現代-領動

指導價:9.98-15.18萬

一說起韓國品牌,就不得不提那大膽設計的外觀了,領動的設計是比較激進的,視覺衝擊力極強,狹長的大燈造型和碩大的六邊形中網給人留下很深刻的印象;純黑的內飾搭配紅色縫線整體感覺非常運動,中控液晶屏幕支持Carplay和CarLife功能,配置上很豐富,旗艦型車型上甚至裝備了主動安全系統。

2700mm的軸距為其乘坐空間提供了保證,462L的後備箱容積和可4/6比例放倒的後排座椅都能滿足日常使用的需求;其中1.4T車型採用了缸內直噴技術,最大功率為130馬力,搭配7速雙離合變速箱,動力的輸出也相對線性,對於渦輪遲滯的控制很好,幾乎感覺不到什麼遲滯現象。

東風雪鐵龍-雪鐵龍C4L

指導價:13.19-18.99萬

新款C4L的前臉變化是很明顯的,採用了上下橫貫式的進氣格柵,從雙立人車標延伸出來的鍍鉻飾條融入大燈內,看起來相當科幻前衛;內飾的變化卻沒外觀來的那麼強烈,與現款相差無幾,一些細節和配置上的升級更人性化了,电子手剎和全新樣式的換擋桿,操作起來更加方便順手了。

車身尺寸為4675*1780*1500mm,軸距達到了2710mm,乘坐空間上是比較寬敞的,加上柔軟的座椅,舒適性表現出色;動力方面,提供了1.2T和1.6T兩款發動機可選,匹配5擋手動或者6擋手自一體變速器,沉穩線性的油門調校讓動力非常容易掌控,動力總體表現出色。

東風本田-思域

指導價:11.59-16.99萬

思域整車採用全新的家族式設計語言打造,相比老款車型更具肌肉感,更多曲麵線條的運用,讓它在外觀方面更加飽滿立體,運動氣息很濃厚;內飾也是一改以往本田的風格,階梯狀設計的中控台錯落有致,大量的軟質材料覆蓋件也提高了不少檔次感,中央液晶儀錶盤功能豐富,显示效果出色。

2700mm的軸距使得新思域的空間有所改善,乘坐空間令人滿意,儲物空間也是比較多,半封閉的中央扶手箱是其中的亮點;1.5T發動機最大功率177馬力,搭配CVT變速器,起步輕盈暢快,動力輸出一氣呵成,渦輪介入感極其輕微,給人加速很線性的體驗,可以說這代思域迎來了脫胎換骨的一代。

總結:三款車的類型各有特色,適合的人群也更廣,配置豐富外觀帥氣的領動,底盤沉穩操控感好的雪鐵龍C4L,動力強勁高顏值的思域,到底哪個才是你的菜!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

※網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

1.5T+CVT還是DCT?這款自主高品質轎車將配新的動力系統

但是就是銷量一般,很大一部分原因是消費者對自主品牌的不信任,也有一些人覺得艾瑞澤7價格稍貴,畢竟它的親弟弟艾瑞澤5的銷量非常好。艾瑞澤7的售價區間為7。99-9。59萬元,1。5T車型的起售價也是為7。99萬元,預測同等配置的1。

最近,我們得到了一些關於奇瑞艾瑞澤7的1.5T自動擋車型的路試照片,新車將會在2017年上市銷售。現款的艾瑞澤7的1.5T車型只配備了5擋手動變速箱,這對於那些既想要大動力又想要自動擋的消費者就感到比較痛苦了。

選擇1.6L車型吧,動力較弱。選擇1.5T車型吧!動力是足夠了,但是就是手動擋開着會比較累。所以奇瑞也意識到了這個問題,抓緊給艾瑞澤7的1.5T車型配備自動擋,豐富自己的產品線,以滿足消費者日趨多樣化的選擇。

從外觀上來看,這次曝光的1.5T自動擋車型和現款車型基本一致,畢竟只是增加了一款車型,又不是改款或者換代,所以外觀是不會有很大變化的。

前臉依舊是“X”造型的設計,前大燈靠內側部分採用了熏黑的處理,以增加運動感。現款車型已經是標配了日間行車燈了,作為自動檔車型自然不可缺少。尾部的“TCI”說明了它是1.5T車型的身份。

內飾也是和現款基本一致,看起來也比較時尚,目測做工應該不錯。主要的變化是由手動擋換車了自動擋。同時配置也比較豐富,ESp、上坡輔助、胎壓監測、倒車雷達、真皮方向盤、GpS、中控大屏幕、藍牙、手機映射等都會配備。

至於動力系統,新車肯定搭載1.5L渦輪增壓發動機,但是變速箱現在還沒有確定的消息,有可能是CVT,也有可能是和瑞虎7一樣的6速雙離合。不過,編者推測雙離合的概率會更大一點。

其實編者一直覺得艾瑞澤7是一款質感非常不錯的車子,四輪獨立懸挂,1.5T發動機動力十足,配置也很厚道,做工也不錯,行駛質感在同級別處於領先地位,操控較好。但是就是銷量一般,很大一部分原因是消費者對自主品牌的不信任,也有一些人覺得艾瑞澤7價格稍貴,畢竟它的親弟弟艾瑞澤5的銷量非常好。

艾瑞澤7的售價區間為7.99-9.59萬元,1.5T車型的起售價也是為7.99萬元,預測同等配置的1.5T自動擋車型將會比手動擋車型最低貴8000元,所以自動擋的售價將會接近9萬元。對於這個價格,你能接受么?

競爭對手

吉利帝豪GL

指導價:7.88-11.38萬

帝豪GL的1.3T自動擋車型的起售價為9.78萬,這價格要比艾瑞澤7稍微貴一點,但是帝豪GL的熱度要比艾瑞澤7大很多。作為目前比較熱門的一款車,帝豪GL將是艾瑞澤7的強大對手。

長安逸動

指導價:8.09-24.99萬

逸動的自動擋車型的起步價為8.99萬元,搭配4AT變速箱,目前還有着七八千的優惠,雖然價格可能比艾瑞澤7便宜,但是逸動的發動機為1.6L 125馬力。

一汽-大眾-寶來

指導價:10.78-15.38萬

寶來自動擋的起售價為11.98萬,目前也有着一兩萬的優惠,是加一些錢買寶來還是直接買艾瑞澤7?這是一個值得思考的問題。

所以說艾瑞澤7上市之後會面臨很多強大的對手,但是編者相信只要價格足夠接地氣,就不用發愁銷量問題。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

這台車日行1200公里 連外國人都驚訝

上汽大通的海外市場表現澳新市場作為上汽大通的第一大海外市場,而且也是唯一獲得大批量歐洲政府採購、唯一進入歐洲國家政府執法機構的中國品牌,同時,在澳洲也成為了銷量第一的中國汽車品牌,在1-9月,上汽大通在澳新市場的銷量達到1791台,同比去年銷量增長了90。

一份值得學習的敬業

戴夫已經在陶朗加(新西蘭北島北部港市)生活了40多年,而對於工作,他說是一種享受,還必不想退休,每天工作的開始先從家中出發,北上漢密爾頓裝貨、再到北邊的奧克蘭進行卸貨和裝報,然後返回陶朗加換乘後半夜的司機繼續南下吉斯伯恩完成《先驅報》的送遞並返回家中。

戴夫從1986年開始從事送遞業務,一直從事報紙信件投遞的工作,30年送遞工作已經累計送達4000萬份貨物。

6年前,戴夫就為新西蘭的第一大報社《先驅報》(日報,日發行量15萬份)進行送貨,這條線路一年下來需要跑45萬公里,從第一次駕駛上汽大通的V80已經有2年多了,不到3年的時間里,前兩周破一百萬公里總行駛里程(三台V80里程合計)。

目前一共有5個司機,實行輪班制,負責陶朗加到漢密爾頓、奧克蘭再南下吉斯伯恩,並最後返回陶朗加約1200公里里程的線路,除了聖誕和復活節,工作風雨無阻,實行做三天休息三天的交替輪班制度,對於已經到了退休年齡的戴夫,還沒有考慮退休必很享受這份工作,會一直開下去。

上汽大通的海外市場表現

澳新市場作為上汽大通的第一大海外市場,而且也是唯一獲得大批量歐洲政府採購、唯一進入歐洲國家政府執法機構的中國品牌,同時,在澳洲也成為了銷量第一的中國汽車品牌,在1-9月,上汽大通在澳新市場的銷量達到1791台,同比去年銷量增長了90.9%,作為上汽大通的第一大海外市場,澳新市場的銷量佔據了上汽大通整個海外銷量的30%。

上汽在澳洲市場的大熱和良好的聲譽,其最根本的原因還是自身產品的性價比高,更合理的市場價,更合理的豐富的配置及良好的安全性和耐用性,都是讓它在市場立足必越來越強的原因,雖然大通進入澳州市場不久,但勢頭穩步上升,可見上汽集團的全球化戰略正初見成效,接下來,上汽大通還將向澳新市場導入一系列新品。包括純電動寬體輕客EV80、中高端皮卡、中大型SUV D90等,隨着新品投放,期待上汽大通如何將代表中國汽車,成為歐美日韓車系之外的又一股強大力量,不僅使中國汽車在澳新市場崛起,並且希望為澳新消費者帶來更多更優秀的產品和服務。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

80后的朋友們別買BBA了 這次給你們推薦點不一樣的

內飾Q50L的內飾風格顯得有些個性,並不如外觀給人感覺那麼的運動,更偏向一台家用車,但由於豪華品牌的定位,用料做工層面還是非常厚道細膩的類型。由於也是前置后驅,後排同樣存在這中間地台較高的特點。動力Q50L搭載的2。

說到30-40萬的豪華品牌車型,不少人第一主觀就會聯想到德系三強BBA,儘管BBA品牌雄踞豪華車市場銷量榜前列多年,但是路面曝光率過高的事實也不免顯得稍微喪失了一些個性,其實在豪華車市場,還有不少新晉的車型。

捷豹XFL

推薦車型:2.0T 200pS風華版

指導價格:38.80萬

捷豹XFL是捷豹品牌在中國首款國產的車型,對標車型是奔馳E級,寶馬5系這類豪華品牌中大型車,但是從定價來看,XFL的入門定價顯得頗為“謙虛”,38.80萬的入門車型配置已經非常不錯。

外觀

XFL的外觀設計呈現的車身姿態比較低矮,真的就宛如一隻卧身伏擊的豹子,囂張的前臉更凸顯了它品牌血統里就富有的攻擊氣質。

車尾設計優雅沉穩,並沒有太多視覺上覺得突兀的地方,或許這也是英國車的一種特點吧;XFL車身修長,超過5米,但並不顯得生拉硬拽,整車比例相當協調,線條勾勒手法流暢柔和,表明了它同樣是一台尺寸傲人的豪華轎車。

內飾

作為最低配的風華版,車內裝飾並沒有使用高配車型上所使用的撞色設計,但根據市場反饋,似乎消費者對於全黑內飾並不會抗拒。

XFL內飾秉承了捷豹家族式的風格,採用了整體環抱式的設計理念,讓前排人員駕乘時的包裹感更加強烈。

由於是前置后驅的驅動形式,後排地板因為傳動軸的原因導致地台較高,乘坐三人稍微有些不便利。

動力

200pS 風華版搭載了一台2.0T渦輪增壓發動機,最大馬力200匹,峰值扭矩320牛米,動力參數上領先於同級別主流的豪華品牌車型。與之匹配的是一台8擋手自一體變速箱,日常行駛動力儲備相當充沛,變速箱的平順性也非常出色。

英菲尼迪Q50L

推薦車型:2016款 2.0T悅享版

指導價格:30.98萬

英菲尼迪是誕生於北美地區的日產豪華品牌,旗下的Q50L是一款性價比較高的中型三廂車,對錶車型是奧迪A4L、寶馬3系這一級別的豪華品牌車型。作為豪華品牌車型,27.98萬的起售門檻還算不錯。

外觀

Q50L同樣也是作為東風英菲尼迪在華國產的首款車型,相較於進口Q50,Q50L的軸距加長了48mm,車身加長50mm,更大尺寸的車身和更長的軸距也讓Q50L更向“豪華”層面靠攏一分。

英菲尼迪Q50L採用了“長車頭短車尾”的造型設計,視覺效果保持一個上揚的態勢,營造出了一種較為兇狠的感覺,車尾緊湊飽滿,外露的雙邊排氣管凸顯了轎跑車型的運動范兒,Q50L偏運動風格的造型也是年輕人更喜歡的類型。

內飾

Q50L的內飾風格顯得有些個性,並不如外觀給人感覺那麼的運動,更偏向一台家用車,但由於豪華品牌的定位,用料做工層面還是非常厚道細膩的類型;由於也是前置后驅,後排同樣存在這中間地台較高的特點。

動力

Q50L搭載的2.0T發動機,最大馬力211匹,峰值扭矩350牛米,匹配的是7速手自一體變速箱,整體調校稍微有些激進,配合上略顯沉重但是十分精準的轉向,Q50L是一台極其富有駕駛樂趣的三廂車。

林肯MKZ

推薦車型:2017款 2.0T尊享版

指導價格:30.48萬

林肯和凱迪拉克一樣,都曾經作為美國總統用車的品牌,只是凱迪拉克在中國市場耕耘已久,而林肯則更多的是作為後起之秀意圖在國內立足。林肯MKZ是目前林肯旗下定位相對親民的一款三廂轎車,起步售價28.48萬,這個價位也引起了不少目標車型在ATS-L、寶馬3系這類車型潛在買家的關注。

外觀:

2017款的MKZ外觀借鑒了林肯自家旗艦車型Continental(大陸)的設計,未來這種尺寸更大,風格更沉穩的進氣格柵設計很有可能成為林肯最新的家族風格。MKZ整體外觀並不激進,圓潤大氣的設計理念讓它彰顯出一種行政級的嚴肅氣質。車尾標誌性的貫穿式尾燈永遠是林肯品牌的辨別武器,就目前來說,一台林肯MKZ還是非常小眾的存在。

內飾:

作為福特旗下的豪華品牌,MKZ的內飾風格看上去高檔感還是相對出色,中間按鍵式的換擋機構對於眾多消費者來說一時間可能難以適應,不過操作便利程度還是很高。由於MKZ是前驅車型,後排地台相對平整,在乘坐空間上的的捨棄程度沒前兩款那麼高,後排空間相對充裕。

動力:

林肯MKZ搭載的是2.0T渦輪增壓發動機,最大馬力203匹,峰值扭矩381牛米,匹配的是6速手自一體變速箱,調校風格也是偏向“福特”的類型,油門輕快,動力輸出很直接。

全文總結:現如今很多80後人群買車的預算區間已經上升到30萬的層級,但是多數人的選擇依舊停留在BBA,不是說BBA不好,而是在崇尚個性化的當下,選擇一款不一樣的,也正是符合年輕人個性化訴求的選擇。

以上三款車型,捷豹接近40萬的售價或許對於大多數人來說還是高了,所以推薦在Q50L和MKZ之間做抉擇;而喜歡駕駛樂趣和操控感的可以考慮Q50L,后驅布局,動力不俗;

偏向沉穩舒適訴求的朋友則可以考慮林肯MKZ,畢竟定位就是一款偏向舒適的中型車。亦個性亦豪華,三十萬級別這兩款都是不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

※台中搬家公司費用怎麼算?

Koa源碼解析,帶你實現一個迷你版的Koa

前言

本文是我在閱讀 Koa 源碼后,並實現迷你版 Koa 的過程。如果你使用過 Koa 但不知道內部的原理,我想這篇文章應該能夠幫助到你,實現一個迷你版的 Koa 不會很難。

本文會循序漸進的解析內部原理,包括:

  1. 基礎版本的 koa
  2. context 的實現
  3. 中間件原理及實現

文件結構

  • application.js: 入口文件,裡面包括我們常用的 use 方法、listen 方法以及對 ctx.body 做輸出處理
  • context.js: 主要是做屬性和方法的代理,讓用戶能夠更簡便的訪問到request和response的屬性和方法
  • request.js: 對原生的 req 屬性做處理,擴展更多可用的屬性和方法,比如:query 屬性、get 方法
  • response.js: 對原生的 res 屬性做處理,擴展更多可用的屬性和方法,比如:status 屬性、set 方法

基礎版本

用法:

const Coa = require('./coa/application')
const app = new Coa()

// 應用中間件
app.use((ctx) => {
  ctx.body = '<h1>Hello</h1>'
})

app.listen(3000, '127.0.0.1')

application.js:

const http = require('http')

module.exports = class Coa {
  use(fn) {
    this.fn = fn
  }
  // listen 只是語法糖  本身還是使用 http.createServer
  listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }
  callback() {
    const handleRequest = (req, res) => {
      // 創建上下文
      const ctx = this.createContext(req, res)
      // 調用中間件
      this.fn(ctx)
      // 輸出內容
      res.end(ctx.body)
    }
    return handleRequest
  }
  createContext(req, res) {
    let ctx = {}
    ctx.req = req
    ctx.res = res
    return ctx
  }
}

基礎版本的實現很簡單,調用 use 將函數存儲起來,在啟動服務器時再執行這個函數,並輸出 ctx.body 的內容。

但是這樣是沒有靈魂的。接下來,實現 context 和中間件原理,Koa 才算完整。

Context

ctx 為我們擴展了很多好用的屬性和方法,比如 ctx.query、ctx.set()。但它們並不是 context 封裝的,而是在訪問 ctx 上的屬性時,它內部通過屬性劫持將 request 和 response 內封裝的屬性返回。就像你訪問 ctx.query,實際上訪問的是 ctx.request.query。

說到劫持你可能會想到 Object.defineProperty,在 Kao 內部使用的是 ES6 提供的對象的 setter 和 getter,效果也是一樣的。所以要實現 ctx,我們首先要實現 request 和 response。

在此之前,需要修改下 createContext 方法:

// 這三個都是對象
const context = require('./context')
const request = require('./request')
const response = require('./response')

module.exports = class Coa {
  constructor() {
    this.context = context
    this.request = request
    this.response = response
  }
  createContext(req, res) {
    const ctx = Object.create(this.context)
    // 將擴展的 request、response 掛載到 ctx 上
    // 使用 Object.create 創建以傳入參數為原型的對象,避免添加屬性時因為衝突影響到原對象
    const request = ctx.request = Object.create(this.request)
    const response = ctx.response = Object.create(this.response)
    
    ctx.app = request.app = response.app = this;
    // 掛載原生屬性
    ctx.req = request.req = response.req = req
    ctx.res = request.res = response.res = res
    
    request.ctx = response.ctx = ctx;
    request.response = response;
    response.request = request;
    
    return ctx
  }
}

上面一堆花里胡哨的賦值,是為了能通過多種途徑獲取屬性。比如獲取 query 屬性,可以有 ctx.query、ctx.request.query、ctx.app.query 等等的方式。

如果你覺得看起來有點冗餘,也可以主要理解這幾行,因為我們實現源碼時也就用到下面這些:

const request = ctx.request = Object.create(this.request)
const response = ctx.response = Object.create(this.response)

ctx.req = request.req = response.req = req
ctx.res = request.res = response.res = res

request

request.js:

const url = require('url')

module.exports = {
 /* 查看這兩步操作
  * const request = ctx.request = Object.create(this.request)
  * ctx.req = request.req = response.req = req 
  * 
  * 此時的 this 是指向 ctx,所以這裏的 this.req 訪問的是原生屬性 req
  * 同樣,也可以通過 this.request.req 來訪問
  */
  // 請求的 query 參數
  get query() {
    return url.parse(this.req.url).query
  },
  // 請求的路徑
  get path() {
    return url.parse(this.req.url).pathname
  },
  // 請求的方法
  get method() {
    return this.req.method.toLowerCase()
  }
}

response

response.js:

module.exports = {
  // 這裏的 this.res 也和上面同理 
  // 返回的狀態碼
  get status() {
    return this.res.statusCode
  },
  set status(val) {
    return this.res.statusCode = val
  },
  // 返回的輸出內容
  get body() {
    return this._body
  },
  set body(val) {
    return this._body = val
  },
  // 設置頭部
  set(filed, val) {
    if (typeof filed === 'string') {
      this.res.setHeader(filed, val)
    }
    if (toString.call(filed) === '[object Object]') {
      for (const key in filed) {
        this.set(key, filed[key])
      }
    }
  }
}

屬性代理

通過上面的實現,我們可以使用 ctx.request.query 來訪問到擴展的屬性。但是在實際應用中,更常用的是 ctx.query。不過 query 是在 request 的屬性,通過 ctx.query 是無法訪問的。

這時只需稍微做個代理,在訪問 ctx.query 時,將 ctx.request.query 返回就可以實現上面的效果。

context.js:

module.exports = {
    get query() {
        return this.request.query
    }
}

實際的代碼中會有很多擴展的屬性,總不可能一個一個去寫吧。為了優雅的代理屬性,Koa 使用 delegates 包實現。這裏我就直接簡單封裝下代理函數,代理函數主要用到__defineGetter__ 和 __defineSetter__ 兩個方法。

在對象上都會帶有 __defineGetter__ 和 __defineSetter__,它們可以將一個函數綁定在當前對象的指定屬性上,當屬性被獲取或賦值時,綁定的函數就會被調用。就像這樣:

let obj = {}
let obj1 = {
    name: 'JoJo'
}
obj.__defineGetter__('name', function(){
    return obj1.name
})

此時訪問 obj.name,獲取到的是 obj1.name 的值。

了解這個兩個方法的用處后,接下來開始修改 context.js:

const proto = module.exports = {
}

// getter代理
function delegateGetter(prop, name){
  proto.__defineGetter__(name, function(){
    return this[prop][name]
  })
}
// setter代理
function delegateSetter(prop, name){
  proto.__defineSetter__(name, function(val){
    return this[prop][name] = val
  })
}
// 方法代理
function delegateMethod(prop, name){
  proto[name] = function() {
    return this[prop][name].apply(this[prop], arguments)
  }
}

delegateGetter('request', 'query')
delegateGetter('request', 'path')
delegateGetter('request', 'method')

delegateGetter('response', 'status')
delegateSetter('response', 'status')
delegateGetter('response', 'body')
delegateSetter('response', 'body')
delegateMethod('response', 'set')

中間件原理

中間件思想是 Koa 最精髓的地方,為擴展功能提供很大的幫助。這也是它雖然小,卻很強大的原因。還有一個優點,中間件使功能模塊的職責更加分明,一個功能就是一个中間件,多个中間件組合起來成為一個完整的應用。

下面是著名的“洋蔥模型”。這幅圖很形象的表達了中間件思想的作用,它就像一個流水線一樣,上游加工后的東西傳遞給下游,下游可以繼續接着加工,最終輸出加工結果。

原理分析

在調用 use 註冊中間件的時候,內部會將每个中間件存儲到數組中,執行中間件時,為其提供 next 參數。調用 next 即執行下一个中間件,以此類推。當數組中的中間件執行完畢后,再原路返回。就像這樣:

app.use((ctx, next) => {
  console.log('1 start')
  next()
  console.log('1 end')
})

app.use((ctx, next) => {
  console.log('2 start')
  next()
  console.log('2 end')
})

app.use((ctx, next) => {
  console.log('3 start')
  next()
  console.log('3 end')
})

輸出結果如下:

1 start
2 start
3 start
3 end
2 end
1 end

有點數據結構知識的同學,很快就想到這是一個“棧”結構,執行的順序符合“先入后出”。

下面我將內部中間件實現原理進行簡化,模擬中間件執行:

function next1() {
  console.log('1 start')
  next2()
  console.log('1 end')
}
function next2() {
  console.log('2 start')
  next3()
  console.log('2 end')
}
function next3() {
  console.log('3 start')
  console.log('3 end')
}
next1()

執行過程:

  1. 調用 next1,將其入棧執行,輸出 1 start
  2. 遇到 next2 函數,將其入棧執行,輸出 2 start
  3. 遇到 next3 函數,將其入棧執行,輸出 3 start
  4. 輸出 3 end,函數執行完畢,next3 彈出棧
  5. 輸出 2 end,函數執行完畢,next2 彈出棧
  6. 輸出 1 end,函數執行完畢,next1 彈出棧
  7. 棧空,全部執行完畢

相信通過這個簡單的例子,都大概明白中間件的執行過程了吧。

原理實現

中間件原理實現的關鍵點主要是 ctx 和 next 的傳遞。

function compose(middleware) {
  return function(ctx) {
    return dispatch(0)
    function dispatch(i){
      // 取出中間件
      let fn = middleware[i]
      if (!fn) {
        return
      }
      // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
      // next 即下一個應用中間件
      fn(ctx, dispatch.bind(null, i + 1))
    }
  }
}

可以看到,實現過程本質是函數的遞歸調用。在內部實現時,其實 next 沒有做什麼神奇的操作,它就是下一个中間件調用的函數,作為參數傳入供使用者調用。

下面我們來單獨測試 compose,你可以將它粘貼到控制台上運行:

function next1(ctx, next) {
  console.log('1 start')
  next()
  console.log('1 end')
}
function next2(ctx, next) {
  console.log('2 start')
  next()
  console.log('2 end')
}
function next3(ctx, next) {
  console.log('3 start')
  next()
  console.log('3 end')
}

let ctx = {}
let fn = compose([next1, next2, next3])
fn(ctx)

最後,因為 Koa 中間件是可以使用 async/await 異步執行的,所以還需要修改下 compose 返回 Promise:

function compose(middleware) {
  return function(ctx) {
    return dispatch(0)
    function dispatch(i){
      // 取出中間件
      let fn = middleware[i]
      if (!fn) {
        return Promise.resolve()
      }
      // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
      // next 即下一個應用中間件
      try {
        return Promise.resolve( fn(ctx, dispatch.bind(null, i + 1)) )
      } catch (error) {
        return Promise.reject(error)
      }
    }
  }
}

應用

實現完成中間件的邏輯后,將它應用到迷你版Koa中,原來的代碼邏輯要做一些修改(部分代碼忽略)

application.js:

module.exports = class Coa {
  constructor() {
    // 存儲中間件的數組 
    this.middleware = []
  }

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    // 將中間件加入數組
    this.middleware.push(fn)
    return this
  }
  
  listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }

  callback() {
    const handleRequest = (req, res) => {
      // 創建上下文
      const ctx = this.createContext(req, res)
      // fn 為第一個應用中間件
      const fn = this.compose(this.middleware)
      // 在所有中間件執行完畢后 respond 函數用於處理 ctx.body 輸出
      return fn(ctx).then(() => respond(ctx)).catch(console.error)
    }
    return handleRequest
  }
  
  compose(middleware) {
    return function(ctx) {
      return dispatch(0)
      function dispatch(i){
        let fn = middleware[i]
        if (!fn) {
          return Promise.resolve()
        }
        // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
        try {
          return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
        } catch (error) {
          return Promise.reject(error)
        }
      }
    }
  }
}

function respond(ctx) {
  let res = ctx.res
  let body = ctx.body
  if (typeof body === 'string') {
    return res.end(body)
  }
  if (typeof body === 'object') {
    return res.end(JSON.stringify(body))
  }
}

完整實現

application.js:

const http = require('http')
const context = require('./context')
const request = require('./request')
const response = require('./response')

module.exports = class Coa {
  constructor() {
    this.middleware = []
    this.context = context
    this.request = request
    this.response = response
  }

  use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    this.middleware.push(fn)
    return this
  }

  listen(...args) {
    const server = http.createServer(this.callback())
    server.listen(...args)
  }

  callback() {
    const handleRequest = (req, res) => {
      // 創建上下文
      const ctx = this.createContext(req, res)
      // fn 為第一個應用中間件
      const fn = this.compose(this.middleware)
      return fn(ctx).then(() => respond(ctx)).catch(console.error)
    }
    return handleRequest
  }

  // 創建上下文
  createContext(req, res) {
    const ctx = Object.create(this.context)
    // 處理過的屬性
    const request = ctx.request = Object.create(this.request)
    const response = ctx.response = Object.create(this.response)
    // 原生屬性
    ctx.app = request.app = response.app = this;
    ctx.req = request.req = response.req = req
    ctx.res = request.res = response.res = res

    request.ctx = response.ctx = ctx;
    request.response = response;
    response.request = request;

    return ctx
  }

  // 中間件處理邏輯實現
  compose(middleware) {
    return function(ctx) {
      return dispatch(0)
      function dispatch(i){
        let fn = middleware[i]
        if (!fn) {
          return Promise.resolve()
        }
        // dispatch.bind(null, i + 1) 為應用中間件接受到的 next
        // next 即下一個應用中間件
        try {
          return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)))
        } catch (error) {
          return Promise.reject(error)
        }
      }
    }
  }
}

// 處理 body 不同類型輸出
function respond(ctx) {
  let res = ctx.res
  let body = ctx.body
  if (typeof body === 'string') {
    return res.end(body)
  }
  if (typeof body === 'object') {
    return res.end(JSON.stringify(body))
  }
}

寫在最後

本文的簡單實現了 Koa 主要的功能。有興趣最好還是自己去看源碼,實現自己的迷你版 Koa。其實 Koa 的源碼不算多,總共4個文件,全部代碼包括註釋也就 1800 行左右。而且邏輯不會很難,很推薦閱讀,尤其適合源碼入門級別的同學觀看。

最後附上完整實現的代碼:github

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

Openshift 4.4 靜態 IP 離線安裝系列:準備離線資源

原文鏈接:https://fuckcloudnative.io/posts/openshift4.4-install-offline-static-1-requirement/

本系列文章描述了離線環境下以 UPI (User Provisioned Infrastructure) 模式安裝 Openshift Container Platform (OCP) 4.4.5 的步驟,我的環境是 VMware ESXI 虛擬化,也適用於其他方式提供的虛擬機或物理主機。離線資源包括安裝鏡像、所有樣例 Image Stream 和 OperatorHub 中的所有 RedHat Operators。

本系列採用靜態 IP 的方式安裝 OCP 集群,如果你可以隨意分配網絡,建議採用 DHCP 的方式。

1. 離線環境

單獨準備一台節點用來執行安裝任務和離線資源準備,這台節點最好具備魔法上網的能力,以便可以同時訪問內外網,我們稱這台節點為基礎節點。

除此之外還需要部署一個私有鏡像倉庫,以供 OCP 安裝和運行時使用,要求支持 version 2 schema 2 (manifest list),我這裏選擇的是 Quay 3.3。鏡像倉庫需要部署在另外一台節點,因為需要用到 443 端口,與後面的負載均衡端口衝突。

很多人誤以為必須聯繫 Red Hat 銷售,簽單之後才能使用 OCP4,其實不然,註冊一個開發者賬號后就可以獲得 quay.io 和 registry.redhat.io 的拉取密鑰了。

2. 準備離線安裝介質

獲取版本信息

目前最新的 OCP 版本是 4.4.5,可以從這裏下載客戶端:

  • https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest-4.4/

解壓出來的二進制文件放到基礎節點的 $PATH 下,看下版本信息:

 → oc adm release info quay.io/openshift-release-dev/ocp-release:4.4.5-x86_64

Name:      4.4.5
Digest:    sha256:4a461dc23a9d323c8bd7a8631bed078a9e5eec690ce073f78b645c83fb4cdf74
Created:   2020-05-21T16:03:01Z
OS/Arch:   linux/amd64
Manifests: 412

Pull From: quay.io/openshift-release-dev/ocp-release@sha256:4a461dc23a9d323c8bd7a8631bed078a9e5eec690ce073f78b645c83fb4cdf74

Release Metadata:
  Version:  4.4.5
  Upgrades: 4.3.18, 4.3.19, 4.3.21, 4.3.22, 4.4.2, 4.4.3, 4.4.4
  Metadata:
    description:
  Metadata:
    url: https://access.redhat.com/errata/RHBA-2020:2180

Component Versions:
  kubernetes 1.17.1
  machine-os 44.81.202005180831-0 Red Hat Enterprise Linux CoreOS

Images:
  NAME                                           DIGEST
  aws-machine-controllers                        sha256:7817d9e707bb51bc1e5110ef66bb67947df42dcf3c9b782a8f12f60b8f229dca
  azure-machine-controllers                      sha256:5e2320f92b7308a4f1ec4aca151c752f69265e8c5b705d78e2f2ee70d717711a
  baremetal-installer                            sha256:4c8c6d2895e065711cfcbffe7e8679d9890480a4975cad683b643d8502375fe3
  baremetal-machine-controllers                  sha256:5f1b312ac47b7f9e91950463e9a4ce5af7094a3a8b0bc064c9b4dcfc9c725ad5
  baremetal-operator                             sha256:a77ff02f349d96567da8e06018ad0dfbfb5fef6600a9a216ade15fadc574f4b4
  baremetal-runtimecfg                           sha256:715bc48eda04afc06827189883451958d8940ed8ab6dd491f602611fe98a6fba
  cli                                            sha256:43159f5486cc113d64d5ba04d781c16a084d18745a911a5ae7200bb895778a72
  cli-artifacts                                  sha256:ce7130db82f5a3bb2c806d7080f356e4c68c0405bf3956d3e290bc2078a8bf32
  cloud-credential-operator                      sha256:244ab9d0fcf7315eb5c399bd3fa7c2e662cf23f87f625757b13f415d484621c3
  cluster-authentication-operator                sha256:3145e4fbd62dde385fd0e33d220c42ec3d00ac1dab72288e584cc502b4b8b6db
  cluster-autoscaler                             sha256:66e47de69f685f2dd063fbce9f4e5a00264a5572140d255f2db4c367cb00bad9
  cluster-autoscaler-operator                    sha256:6a32eafdbea3d12c0681a1a1660c7a424f7082a1c42e22d1b301ab0ab6da191b
  cluster-bootstrap                              sha256:fbde2b1a3df7172ce5dbc5e8818bfe631718399eda8058b301a1ef059f549e95
  cluster-config-operator                        sha256:5437794d2309ebe65ca08d1bdeb9fcd665732207b3287df8a7c56e5a2813eccb
  cluster-csi-snapshot-controller-operator       sha256:bc4d8ad97b473316518dbd8906dd900feba383425671eb7d4d73ed1d705c105e
  cluster-dns-operator                           sha256:1a7469258e351d2d56a98a5ef4a3dfa0326b4677fdc1dd11279b6a193ccdbad1
  cluster-etcd-operator                          sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77
  cluster-image-registry-operator                sha256:0aaa817389487d266faf89cecbfd3197405d87172ee2dcda169dfa90e2e9ca18
  cluster-ingress-operator                       sha256:4887544363e052e656aa1fd44d2844226ee2e4617e08b88ba0211a93bb3101fa
  cluster-kube-apiserver-operator                sha256:718ca346d5499cccb4de98c1f858c9a9a13bbf429624226f466c3ee2c14ebf40
  cluster-kube-controller-manager-operator       sha256:0aa16b4ff32fbb9bc7b32aa1bf6441a19a1deb775fb203f21bb8792ff1a26c2e
  cluster-kube-scheduler-operator                sha256:887eda5ce495f1a33c5adbba8772064d3a8b78192162e4c75bd84763c5a1fb01
  cluster-kube-storage-version-migrator-operator sha256:0fd3e25304a6e23e9699172a84dc134b9b5b81dd89496322a9f46f4cd82ecf71
  cluster-machine-approver                       sha256:c35b382d426ff03cfe07719f19e871ec3bd4189fa27452b3e2eb2fb4ab085afc
  cluster-monitoring-operator                    sha256:d7d5f3b6094c88cb1aa9d5bf1b29c574f13db7142e0a9fba03c6681fe4b592a5
  cluster-network-operator                       sha256:563018341e5b37e5cf370ee0a112aa85dd5e17a658b303714252cc59ddfadea5
  cluster-node-tuned                             sha256:0d1a3f66cd7cfc889ddf17cbdb4cb2e4b9188c341b165de1c9c1df578fb53212
  cluster-node-tuning-operator                   sha256:8e00331fd6b725b1d44687bafa2186920e2864fd4d04869ad4e9f5ba56d663ca
  cluster-openshift-apiserver-operator           sha256:087dd3801b15ca614be0998615a0d827383e9c9ab39e64107324074bddccfff8
  cluster-openshift-controller-manager-operator  sha256:a25afbcb148f3535372784e82c66a6cc2843fe9e7119b9198a39422edb95c2ae
  cluster-policy-controller                      sha256:6294d4af2061d23f52a2a439d20272280aa6e5fcff7a5559b4797fb8e6536790
  cluster-samples-operator                       sha256:7040633af70ceb19147687d948a389d392945cb57236165409e66e5101c0d0c0
  cluster-storage-operator                       sha256:bcfeab624513563c9e26629be2914770436c49318c321bd99028a7d1ffab30cf
  cluster-svcat-apiserver-operator               sha256:21a562f26c967ad6d83e1f4219fad858154c3df9854f1462331b244906c6ca9c
  cluster-svcat-controller-manager-operator      sha256:b635529e5843996a51ace6a2aea4854e46256669ef1773c7371e4f0407dbf843
  cluster-update-keys                            sha256:828e11d8132caf5533e18b8e5d292d56ccf52b08e4fe4c53d7825404b05b2844
  cluster-version-operator                       sha256:7a2a210bc07fead80b3f4276cf14692c39a70640a124326ee919d415f0dc5b2c
  configmap-reloader                             sha256:07d46699cb9810e3f629b5142a571db83106aa1190d5177a9944272080cd053d
  console                                        sha256:69f14151fe8681e5fa48912f8f4df753a0dcc3d616ad7991c463402517d1eab4
  console-operator                               sha256:85c9a48c9b1896f36cf061bd4890e7f85e0dc383148f2a1dc498e668dee961df
  container-networking-plugins                   sha256:1a2ecb28b80800c327ad79fb4c8fb6cc9f0b434fc42a4de5b663b907852ee9fb
  coredns                                        sha256:b25b8b2219e8c247c088af93e833c9ac390bc63459955e131d89b77c485d144d
  csi-snapshot-controller                        sha256:33f89dbd081d119aac8d7c56abcb060906b23d31bc801091b789dea14190493f
  deployer                                       sha256:b24cd515360ae4eba89d4d92afe2689a84043106f7defe34df28acf252cd45b4
  docker-builder                                 sha256:d3cf4e3ad3c3ce4bef52d9543c87a1c555861b726ac9cae0cc57486be1095f8a
  docker-registry                                sha256:8b6ab4a0c14118020fa56b70cab440883045003a8d9304c96691a0401ad7117c
  etcd                                           sha256:aba3c59eb6d088d61b268f83b034230b3396ce67da4f6f6d49201e55efebc6b2
  gcp-machine-controllers                        sha256:1c67b5186bbbdc6f424d611eeff83f11e1985847f4a98f82642dcd0938757b0e
  grafana                                        sha256:aa5c9d3d828b04418d17a4bc3a37043413bdd7c036a75c41cd5f57d8db8aa25a
  haproxy-router                                 sha256:7064737dd9d0a43de7a87a094487ab4d7b9e666675c53cf4806d1c9279bd6c2e
  hyperkube                                      sha256:187b9d29fea1bde9f1785584b4a7bbf9a0b9f93e1323d92d138e61c861b6286c
  insights-operator                              sha256:51dc869dc1a105165543d12eeee8229916fc15387210edc6702dbc944f7cedd7
  installer                                      sha256:a0f23a3292a23257a16189bdae75f7b5413364799e67a480dfad086737e248e0
  installer-artifacts                            sha256:afe926af218d506a7f64ef3df0d949aa6653a311a320bc833398512d1f000645
  ironic                                         sha256:80087bd97c28c69fc08cd291f6115b0e12698abf2e87a3d2bbe0e64f600bae93
  ironic-hardware-inventory-recorder             sha256:2336af8eb4949ec283dc22865637e3fec80a4f6b1d3b78178d58ea05afbd49c2
  ironic-inspector                               sha256:1f48cc344aab15c107e2fb381f9825613f586e116c218cdaf18d1e67b13e2252
  ironic-ipa-downloader                          sha256:a417b910e06ad030b480988d6864367c604027d6476e02e0c3d5dcd6f6ab4ccb
  ironic-machine-os-downloader                   sha256:10b751d8e4ba2975dabc256c7ac4dcf94f4de99be35242505bf8db922e968403
  ironic-static-ip-manager                       sha256:0c122317e3a6407a56a16067d518c18ce08f883883745b2e11a5a39ff695d3d0
  jenkins                                        sha256:d4ab77a119479a95a33beac0d94980a7a0a87cf792f5850b30dff4f1f90a9c4d
  jenkins-agent-maven                            sha256:10559ec206191a9931b1044260007fe8dcedacb8b171be737dfb1ccca9bbf0f5
  jenkins-agent-nodejs                           sha256:ad9e83ea1ea3f338af4dbc9461f8b243bd817df722909293fde33b4f9cbab2bc
  k8s-prometheus-adapter                         sha256:be548d31a65e56234e4b98d6541a14936bc0135875ec61e068578f7014aac31e
  keepalived-ipfailover                          sha256:a882a11b55b2fc41b538b59bf5db8e4cfc47c537890e4906fe6bf22f9da75575
  kube-client-agent                              sha256:8eb481214103d8e0b5fe982ffd682f838b969c8ff7d4f3ed4f83d4a444fb841b
  kube-etcd-signer-server                        sha256:8468b1c575906ed41aa7c3ac3b0a440bf3bc254d2975ecc5e23f84aa54395c81
  kube-proxy                                     sha256:886ae5bd5777773c7ef2fc76f1100cc8f592653ce46f73b816de80a20a113769
  kube-rbac-proxy                                sha256:f6351c3aa750fea93050673f66c5ddaaf9e1db241c7ebe31f555e011b20d8c30
  kube-state-metrics                             sha256:ca47160369e67e1d502e93175f6360645ae02933cceddadedabe53cd874f0f89
  kube-storage-version-migrator                  sha256:319e88c22ea618e7b013166eace41c52eb70c8ad950868205f52385f09e96023
  kuryr-cni                                      sha256:3eecf00fdfca50e90ba2d659bd765eb04b5c446579e121656badcfd41da87663
  kuryr-controller                               sha256:7d70c92699a69a589a3c2e1045a16855ba02af39ce09d6a6df9b1dbabacff4f5
  libvirt-machine-controllers                    sha256:cc3c7778de8d9e8e4ed543655392f942d871317f4b3b7ed31208312b4cc2e61f
  local-storage-static-provisioner               sha256:a7ff3ec289d426c7aaee35a459ef8c862b744d709099dedcd98a4579136f7d47
  machine-api-operator                           sha256:4ca2f1b93ad00364c053592aea0992bbb3cb4b2ea2f7d1d1af286c26659c11d3
  machine-config-operator                        sha256:31dfdca3584982ed5a82d3017322b7d65a491ab25080c427f3f07d9ce93c52e2
  machine-os-content                             sha256:b397960b7cc14c2e2603111b7385c6e8e4b0f683f9873cd9252a789175e5c4e1
  mdns-publisher                                 sha256:dea1fcb456eae4aabdf5d2d5c537a968a2dafc3da52fe20e8d99a176fccaabce
  multus-admission-controller                    sha256:377ed5566c062bd2a677ddc0c962924c81796f8d45346b2eefedf5350d7de6b3
  multus-cni                                     sha256:bc58468a736e75083e0771d88095229bdd6c1e58db8aa33ef60b326e0bfaf271
  multus-route-override-cni                      sha256:e078599fde3b974832c06312973fae7ed93334ea30247b11b9f1861e2b0da7d6
  multus-whereabouts-ipam-cni                    sha256:89c386f5c3940d88d9bc2520f422a2983514f928585a51ae376c43f19e5a6cad
  must-gather                                    sha256:a295d2568410a45f1ab403173ee84d7012bb3ec010c24aa0a17925d08d726e20
  oauth-proxy                                    sha256:619bdb128e410b52451dbf79c9efb089e138127812da19a1f69907117480827f
  oauth-server                                   sha256:58545567c899686cae51d2de4e53a5d49323183a7a3065c0b96ad674686acbe8
  openshift-apiserver                            sha256:8fd79797e6e0e9337fc9689863c3817540a003685a6dfc2a55ecb77059967cef
  openshift-controller-manager                   sha256:4485d6eb7625becf581473690858a01ab83244ecb03bb0319bf849068e98a86a
  openshift-state-metrics                        sha256:6de02ce03089b715e9f767142de33f006809226f037fe21544e1f79755ade920
  openstack-machine-controllers                  sha256:d61e611416196650c81174967e5f11cbdc051d696e38ba341de169375d985709
  operator-lifecycle-manager                     sha256:6e1bca545c35fb7ae4d0f57006acce9a9fabce792c4026944da68d7ddfdec244
  operator-marketplace                           sha256:f0750960873a7cc96f7106e20ea260dd41c09b8a30ce714092d3dcd8a7ec396d
  operator-registry                              sha256:7914f42c9274d263c6ba8623db8e6af4940753dcb4160deb291a9cbc61487414
  ovirt-machine-controllers                      sha256:44f9e65ccd39858bf3d7aa2929f5feac634407e36f912ca88585b445d161506c
  ovn-kubernetes                                 sha256:d80899ed1a6a9f99eb8c64856cd4e576f6534b7390777f3180afb8a634743d62
  pod                                            sha256:d7862a735f492a18cb127742b5c2252281aa8f3bd92189176dd46ae9620ee68a
  prom-label-proxy                               sha256:1cf614e8acbe3bcca3978a07489cd47627f3a3bd132a5c2fe0072d9e3e797210
  prometheus                                     sha256:5eea86e59ffb32fca37cacff22ad00838ea6b947272138f8a56062f68ec40c28
  prometheus-alertmanager                        sha256:bb710e91873ad50ac10c2821b2a28c29e5b89b5da7740a920235ecc33fb063f5
  prometheus-config-reloader                     sha256:7cadb408d7c78440ddacf2770028ee0389b6840651c753f4b24032548f56b7aa
  prometheus-node-exporter                       sha256:7d4e76fea0786f4025e37b5ad0fb30498db5586183fc560554626e91066f60f3
  prometheus-operator                            sha256:6e599a9a8691cce0b40bf1ac5373ddb8009113a2115b5617b2d3a3996174c8f7
  sdn                                            sha256:08c256b7b07c57f195faa33ea4273694dd3504d4a85a10dbf7616b91eaa8e661
  service-ca-operator                            sha256:8c9a3071040f956cce15d1e6da70f6f47dc55b609e4f19fe469ce581cd42bfe5
  service-catalog                                sha256:d9a5fbf60e3bbf1c9811e1707ce9bd04e8263552ba3a6bea8f8c7b604808fdf9
  telemeter                                      sha256:19cfc3e37e12d9dd4e4dd9307781368bbeb07929b6ab788e99aa5543badee3c9
  tests                                          sha256:fc56c9805e2e4a8416c1c5433d7974148f0bad88be4a62feeedcd5d9db4b6ad6
  thanos                                         sha256:a4ea116aec2f972991f5a22f39aa1dbc567dddc3429ddca873601714d003a51c

創建內部鏡像倉庫

內部鏡像倉庫用於存放部署 OCP 集群所需的鏡像,倉庫本身使用 Quay 部署。Quay 包含了幾個核心組件:

  • 數據庫 : 主要存放鏡像倉庫的元數據(非鏡像存儲)
  • Redis : 存放構建日誌和Quay的嚮導
  • Quay : 作為鏡像倉庫
  • Clair : 提供鏡像掃描功能

首先修改鏡像倉庫節點的主機名:

$ hostnamectl set-hostname registry.openshift4.example.com

所有節點主機名都要採用三級域名格式,如 master1.aa.bb.com。

接着安裝 podman:

$ yum install -y podman

先創建一個 Pod,用來共享 Network Namespace:

 → podman pod create --name quay -p 443:8443

安裝 Mysql 數據庫:

$ mkdir -p /data/quay/lib/mysql
$ chmod 777 /data/quay/lib/mysql
$ export MYSQL_CONTAINER_NAME=quay-mysql
$ export MYSQL_DATABASE=enterpriseregistrydb
$ export MYSQL_PASSWORD=<PASSWD>
$ export MYSQL_USER=quayuser
$ export MYSQL_ROOT_PASSWORD=<PASSWD>
$ podman run \
    --detach \
    --restart=always \
    --env MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} \
    --env MYSQL_USER=${MYSQL_USER} \
    --env MYSQL_PASSWORD=${MYSQL_PASSWORD} \
    --env MYSQL_DATABASE=${MYSQL_DATABASE} \
    --name ${MYSQL_CONTAINER_NAME} \
    --privileged=true \
    --pod quay \
    -v /data/quay/lib/mysql:/var/lib/mysql/data:Z \
    registry.access.redhat.com/rhscl/mysql-57-rhel7

安裝 Redis:

$ mkdir -p /data/quay/lib/redis
$ chmod 777 /data/quay/lib/redis
$ podman run -d --restart=always \
    --pod quay \
    --privileged=true \
    --name quay-redis \
    -v  /data/quay/lib/redis:/var/lib/redis/data:Z \
    registry.access.redhat.com/rhscl/redis-32-rhel7

獲取 Red Hat Quay v3 鏡像的訪問權:

$ podman login -u="redhat+quay" -p="O81WSHRSJR14UAZBK54GQHJS0P1V4CLWAJV1X2C4SD7KO59CQ9N3RE12612XU1HR" quay.io

參考:https://access.redhat.com/solutions/3533201

配置 Quay:

$ podman run --privileged=true \
    --name quay-config \
    --pod quay \
    --add-host mysql:127.0.0.1 \
    --add-host redis:127.0.0.1 \
    --add-host clair:127.0.0.1 \
    -d quay.io/redhat/quay:v3.3.0 config fuckcloudnative.io

這一步會啟動一個配置 Quay 的進程,打開瀏覽器訪問:https://registry.openshift4.example.com,用戶名/密碼為:quayconfig/fuckcloudnative.io:

選擇新建配置,然後設置數據庫:

設置超級管理員:

下一個界面要設置兩個地方,一個是 Server configuration 的 Server Hostname,另一個是 Redis Hostname,SSL 不用設置,後面直接通過命令行配置:

配置檢查通過後,就可以保存下載下來:

最後會導出一個 quay-config.tar.gz,將其上傳到 Quay 所在的服務器,解壓到配置文件目錄:

$ mkdir -p /data/quay/config
$ mkdir -p /data/quay/storage
$ cp quay-config.tar.gz /data/quay/config/
$ cd /data/quay/config/
$ tar zxvf quay-config.tar.gz

生成自簽名證書:

# 生成私鑰
$ openssl genrsa -out ssl.key 1024

根據私鑰生成證書申請文件 csr:

$ openssl req -new -key ssl.key -out ssl.csr

這裏根據命令行嚮導來進行信息輸入:

Common Name 可以輸入:*.yourdomain.com,這種方式可以生成通配符域名證書。

使用私鑰對證書申請進行簽名從而生成證書:

$ openssl x509 -req -in ssl.csr -out ssl.cert -signkey ssl.key -days 3650

這樣就生成了有效期為 10 年的證書文件,對於自己內網服務使用足夠。

或者你也可以一步到位:

$ openssl req \
  -newkey rsa:2048 -nodes -keyout ssl.key \
  -x509 -days 3650 -out ssl.cert -subj \
  "/C=CN/ST=Shanghai/L=Shanghai/O=IBM/OU=IBM/CN=*.openshift4.example.com"

證書搞定了之後,還需要修改 config.yaml,將協議修改為 https:

PREFERRED_URL_SCHEME: https

然後停止 quay-config:

$ podman stop quay-config

最後一步才是部署 Quay:

$ podman run --restart=always \
    --sysctl net.core.somaxconn=4096 \
    --privileged=true \
    --name quay-master \
    --pod quay \
    --add-host mysql:127.0.0.1 \
    --add-host redis:127.0.0.1 \
    --add-host clair:127.0.0.1 \
    -v /data/quay/config:/conf/stack:Z \
    -v /data/quay/storage:/datastorage:Z \
    -d quay.io/redhat/quay:v3.3.0

安裝成功后,將自簽名的證書複製到默認信任證書路徑:

$ cp ssl.cert /etc/pki/ca-trust/source/anchors/ssl.crt
$ update-ca-trust extract

現在可以通過 podman login 命令來測試倉庫的連通性,看到如下字樣即表示安裝成功(也可以通過瀏覽器訪問 Web UI):

 → podman login registry.openshift4.example.com
Username: admin
Password: ********

Login Succeeded

如果使用 Docker 登錄,需要將證書複製到 docker 的信任證書路徑:

$ mkdir -p /etc/docker/certs.d/registry.openshift4.example.com
$ cp ssl.cert /etc/docker/certs.d/registry.openshift4.example.com/ssl.crt
$ systemctl restart docker

下載鏡像文件

準備拉取鏡像權限認證文件。 從 Red Hat OpenShift Cluster Manager 站點的 Pull Secret 頁面下載 registry.redhat.io 的 pull secret。

# 把下載的 txt 文件轉出 json 格式,如果沒有 jq 命令,通過 epel 源安裝
$ cat ./pull-secret.txt | jq . > pull-secret.json

$ yum install epel-release
$ yum install jq

JSON 內容如下:

{
  "auths": {
    "cloud.openshift.com": {
      "auth": "b3BlbnNo...",
      "email": "you@example.com"
    },
    "quay.io": {
      "auth": "b3BlbnNo...",
      "email": "you@example.com"
    },
    "registry.connect.redhat.com": {
      "auth": "NTE3Njg5Nj...",
      "email": "you@example.com"
    },
    "registry.redhat.io": {
      "auth": "NTE3Njg5Nj...",
      "email": "you@example.com"
    }
  }
}

把本地倉庫的用戶密碼轉換成 base64 編碼:

$ echo -n 'admin:password' | base64 -w0 
cm9vdDpwYXNzd29yZA==

然後在 pull-secret.json 裏面加一段本地倉庫的權限。第一行倉庫域名和端口,第二行是上面的 base64,第三行隨便填個郵箱:

  "auths": {
...
    "registry.openshift4.example.com": {
      "auth": "cm9vdDpwYXNzd29yZA==",
      "email": "you@example.com"
   },
...

設置環境變量:

$ export OCP_RELEASE="4.4.5-x86_64"
$ export LOCAL_REGISTRY='registry.openshift4.example.com' 
$ export LOCAL_REPOSITORY='ocp4/openshift4'
$ export PRODUCT_REPO='openshift-release-dev'
$ export LOCAL_SECRET_JSON='/root/pull-secret.json'
$ export RELEASE_NAME="ocp-release"
  • OCP_RELEASE : OCP 版本,可以在這個頁面查看。如果版本不對,下面執行 oc adm 時會提示 image does not exist。
  • LOCAL_REGISTRY : 本地倉庫的域名和端口。
  • LOCAL_REPOSITORY : 鏡像存儲庫名稱,使用 ocp4/openshift4。
  • PRODUCT_REPO 和 RELEASE_NAME 都不需要改,這些都是一些版本特徵,保持不變即可。
  • LOCAL_SECRET_JSON : 密鑰路徑,就是上面 pull-secret.json 的存放路徑。

在 Quay 中創建一個組織(Organization)ocp4 用來存放同步過來的鏡像。

最後一步就是同步鏡像,這一步的動作就是把 quay 官方倉庫中的鏡像同步到本地倉庫,如果失敗了可以重新執行命令,整體內容大概 5G。

$ oc adm -a ${LOCAL_SECRET_JSON} release mirror \
     --from=quay.io/${PRODUCT_REPO}/${RELEASE_NAME}:${OCP_RELEASE} \
     --to=${LOCAL_REGISTRY}/${LOCAL_REPOSITORY} \
     --to-release-image=${LOCAL_REGISTRY}/${LOCAL_REPOSITORY}:${OCP_RELEASE}

oc adm release mirror 命令執行完成後會輸出下面類似的信息,保存下來,將來會用在 install-config.yaml 文件中:

imageContentSources:
- mirrors:
  - registry.openshift4.example.com/ocp4/openshift4
  source: quay.io/openshift-release-dev/ocp-release
- mirrors:
  - registry.openshift4.example.com/ocp4/openshift4
  source: quay.io/openshift-release-dev/ocp-v4.0-art-dev

本地鏡像倉庫緩存好鏡像之後,通過 tag/list 接口查看所有 tag,如果能列出來一堆就說明是正常的:

$ curl -s -X GET -H "Authorization: Bearer <token>" https://registry.openshift4.example.com/api/v1/repository/ocp4/openshift4/tag/|jq .

{
  "has_additional": true,
  "page": 1,
  "tags": [
    {
      "name": "4.4.5-cluster-kube-scheduler-operator",
      "reversion": false,
      "start_ts": 1590821178,
      "image_id": "a778898a93d4fc5413abea38aa604d14d7efbd99ee1ea75d2d1bea3c27a05859",
      "last_modified": "Sat, 30 May 2020 06:46:18 -0000",
      "manifest_digest": "sha256:887eda5ce495f1a33c5adbba8772064d3a8b78192162e4c75bd84763c5a1fb01",
      "docker_image_id": "a778898a93d4fc5413abea38aa604d14d7efbd99ee1ea75d2d1bea3c27a05859",
      "is_manifest_list": false,
      "size": 103582366
    },
    {
      "name": "4.4.5-kube-rbac-proxy",
      "reversion": false,
      "start_ts": 1590821178,
      "image_id": "f1714cda6028bd7998fbba1eb79348f33b9ed9ccb0a69388da2eb0aefc222f85",
      "last_modified": "Sat, 30 May 2020 06:46:18 -0000",
      "manifest_digest": "sha256:f6351c3aa750fea93050673f66c5ddaaf9e1db241c7ebe31f555e011b20d8c30",
      "docker_image_id": "f1714cda6028bd7998fbba1eb79348f33b9ed9ccb0a69388da2eb0aefc222f85",
      "is_manifest_list": false,
      "size": 102366055
    },
    {
      "name": "4.4.5-cluster-kube-controller-manager-operator",
      "reversion": false,
      "start_ts": 1590821178,
      "image_id": "bc7e19d35ec08c1a93058db1705998da2f8bbe5cdbb7f3f5974e6176e2f79eb6",
      "last_modified": "Sat, 30 May 2020 06:46:18 -0000",
      "manifest_digest": "sha256:0aa16b4ff32fbb9bc7b32aa1bf6441a19a1deb775fb203f21bb8792ff1a26c2e",
      "docker_image_id": "bc7e19d35ec08c1a93058db1705998da2f8bbe5cdbb7f3f5974e6176e2f79eb6",
      "is_manifest_list": false,
      "size": 104264263
    },
    {
      "name": "4.4.5-baremetal-operator",
      "reversion": false,
      "start_ts": 1590821178,
      "image_id": "6ec90c0fb53125801d41b37f8f28c4679e49ce19427f7848803a2bc397e4c23b",
      "last_modified": "Sat, 30 May 2020 06:46:18 -0000",
      "manifest_digest": "sha256:a77ff02f349d96567da8e06018ad0dfbfb5fef6600a9a216ade15fadc574f4b4",
      "docker_image_id": "6ec90c0fb53125801d41b37f8f28c4679e49ce19427f7848803a2bc397e4c23b",
      "is_manifest_list": false,
      "size": 110117444
    },
    {
      "name": "4.4.5-cluster-etcd-operator",
      "reversion": false,
      "start_ts": 1590821178,
      "image_id": "d0cf3539496e075954e53fce5ed56445ae87f9f32cfb41e9352a23af4aa04d69",
      "last_modified": "Sat, 30 May 2020 06:46:18 -0000",
      "manifest_digest": "sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77",
      "docker_image_id": "d0cf3539496e075954e53fce5ed56445ae87f9f32cfb41e9352a23af4aa04d69",
      "is_manifest_list": false,
      "size": 103890103
    },
    {
      "name": "4.4.5-openshift-apiserver",
      "reversion": false,
      "start_ts": 1590821177,
      "image_id": "eba5a051dcbab534228728c7295d31edc0323c7930fa44b40059cf8d22948363",
      "last_modified": "Sat, 30 May 2020 06:46:17 -0000",
      "manifest_digest": "sha256:8fd79797e6e0e9337fc9689863c3817540a003685a6dfc2a55ecb77059967cef",
      "docker_image_id": "eba5a051dcbab534228728c7295d31edc0323c7930fa44b40059cf8d22948363",
      "is_manifest_list": false,
      "size": 109243025
    },
    {
      "name": "4.4.5-kube-client-agent",
      "reversion": false,
      "start_ts": 1590821177,
      "image_id": "fc1fdfb96e9cd250024094b15efa79344c955c7d0c93253df312ffdae02b5524",
      "last_modified": "Sat, 30 May 2020 06:46:17 -0000",
      "manifest_digest": "sha256:8eb481214103d8e0b5fe982ffd682f838b969c8ff7d4f3ed4f83d4a444fb841b",
      "docker_image_id": "fc1fdfb96e9cd250024094b15efa79344c955c7d0c93253df312ffdae02b5524",
      "is_manifest_list": false,
      "size": 99721802
    },
    {
      "name": "4.4.5-kube-proxy",
      "reversion": false,
      "start_ts": 1590821177,
      "image_id": "d2577f4816cb81444ef3b441bf9769904c602cd6626982c2fd8ebba162fd0c08",
      "last_modified": "Sat, 30 May 2020 06:46:17 -0000",
      "manifest_digest": "sha256:886ae5bd5777773c7ef2fc76f1100cc8f592653ce46f73b816de80a20a113769",
      "docker_image_id": "d2577f4816cb81444ef3b441bf9769904c602cd6626982c2fd8ebba162fd0c08",
      "is_manifest_list": false,
      "size": 103473573
    },
    ...
}

這裏需要創建一個 OAuth access token 來訪問 Quay 的 API,創建過程如下:

  1. 瀏覽器登錄 Red Hat Quay,選擇一個組織(Organization),例如 ocp4。
  2. 在左側導航中選擇 Applications 圖標。
  3. 選擇 Create New Application,輸入 Application 的名字然後回車。
  4. 選擇你新創建的 Application,在左側導航欄中選擇 Generate Token。
  5. 選擇相應的權限,然後點擊 Generate Access Token。
  6. 再次確認你設置的權限,然後點擊 Authorize Application。
  7. 保管好生成的 token。

Quay 的 API 文檔可以參考這裏:Appendix A: Red Hat Quay Application Programming Interface (API)。

Quay 中也能看到所有的鏡像:

提取 openshift-install 命令

為了保證安裝版本一致性,需要從鏡像庫中提取 openshift-install 二進制文件,不能直接從 https://mirror.openshift.com/pub/openshift-v4/clients/ocp/4.4.5 下載,不然後面會有 sha256 匹配不上的問題。

# 這一步需要用到上面的 export 變量
$ oc adm release extract \
  -a ${LOCAL_SECRET_JSON} \
  --command=openshift-install \
  "${LOCAL_REGISTRY}/${LOCAL_REPOSITORY}:${OCP_RELEASE}"

如果提示 error: image dose not exist,說明拉取的鏡像不全,或者版本不對。

把文件移動到 $PATH 並確認版本:

$ chmod +x openshift-install
$ mv openshift-install /usr/local/bin/

$ openshift-install version
openshift-install 4.4.5
built from commit 15eac3785998a5bc250c9f72101a4a9cb767e494
release image registry.openshift4.example.com/ocp4/openshift4@sha256:4a461dc23a9d323c8bd7a8631bed078a9e5eec690ce073f78b645c83fb4cdf74

3. 準備 Image Stream 樣例鏡像

準備一個鏡像列表,然後使用 oc image mirror 將鏡像同步到私有倉庫中:

cat sample-images.txt | while read line; do
  target=$(echo $line | sed 's/registry.redhat.io/registry.openshift4.example.com/')
  oc image mirror -a ${LOCAL_SECRET_JSON} $line $target
done

如果之前裝過 OCP 4.4.5,把 openshift-cluster-samples-operator 項目下 cluster-samples-operator Pod 的 /opt/openshift 目錄同步出來,簡單 grep 一下就都有了完整的鏡像列表。

完整列表參考這裏。

同步過程中如果遇到報錯,可根據報錯信息到 Quay 中創建相應的 Organization,不用中斷任務。這裏給出一個參考,需要創建以下的 Organization:

rhscl
jboss-datavirt-6
3scale-amp21
3scale-amp22
3scale-amp23
3scale-amp24
3scale-amp25
3scale-amp26
jboss-eap-6
devtools
openshift3
rhpam-7
rhdm-7
jboss-amq-6
jboss-datagrid-7
jboss-datagrid-6
jboss-webserver-3
amq-broker-7
jboss-webserver-5
redhat-sso-7
openjdk
redhat-openjdk-18
fuse7
dotnet

4. 準備 OperatorHub 離線資源

首先在 Quay 中創建一個 devinfra 項目,然後構建 RedHat Operators 的 catalog image, 保存為 registry.openshift4.example.com/devinfra/redhat-operators:v1。

$ oc adm catalog build \
  -a ${LOCAL_SECRET_JSON} \
  --appregistry-endpoint https://quay.io/cnr \
  --from=registry.redhat.io/openshift4/ose-operator-registry:v4.4 \
  --appregistry-org redhat-operators \
  --to=registry.openshift4.example.com/devinfra/redhat-operators:v1

這個 catalog image 相當於 RedHat Operators 的一個目錄,通過 catalog image 可以找到 RedHat Operators 的所有鏡像。而且 catalog image 使用 sha256 digest 來引用鏡像,能夠確保應用有穩定可重複的部署。

然後使用 catalog image 同步 RedHat Operators 的所有鏡像到私有倉庫:

$ oc adm catalog mirror \
  -a ${LOCAL_SECRET_JSON} \
  registry.openshift4.example.com/devinfra/redhat-operators:v1 \
  registry.openshift4.example.com

如果執行過程中遇到 project not found 之類的錯誤,可根據報錯信息到 Quay 中創建相應的項目,不用中斷任務。

這裏還會遇到一個 bug,執行到最後會有如下的報錯信息:

...
I0409 08:04:48.342110   11331 mirror.go:231] wrote database to /tmp/db-225652515/bundles.db
W0409 08:04:48.347417   11331 mirror.go:258] errors during mirroring. the full contents of the catalog may not have been mirrored: couldn't parse image for mirroring (), skipping mirror: invalid reference format
I0409 08:04:48.385816   11331 mirror.go:329] wrote mirroring manifests to redhat-operators-manifests

先來看看有哪些 Operators:

$ sqlite3 /tmp/db-225652515/bundles.db 'select * from related_image'|grep '^|'

隨便挑一個 Operator,查看其 ClusterServiceVersion 的 spec.relatedImages 字段內容:

$ cat /tmp/cache-943388495/manifests-698804708/3scale-operator/3scale-operator-9re7jpyl/0.5.0/3scale-operator.v0.5.0.clusterserviceversion.yaml

...
spec:
  replaces: 3scale-operator.v0.4.2
  relatedImages:
  - name: apicast-gateway-rhel8
    image: registry.redhat.io/3scale-amp2/apicast-gateway-rhel8@sha256:21be62a6557846337dc0cf764be63442718fab03b95c198a301363886a9e74f9
  - name: backend-rhel7
    image: registry.redhat.io/3scale-amp2/backend-rhel7@sha256:ea8a31345d3c2a56b02998b019db2e17f61eeaa26790a07962d5e3b66032d8e5
  - name: system-rhel7
    image: registry.redhat.io/3scale-amp2/system-rhel7@sha256:93819c324831353bb8f7cb6e9910694b88609c3a20d4c1b9a22d9c2bbfbad16f
  - name: zync-rhel7
    image: registry.redhat.io/3scale-amp2/zync-rhel7@sha256:f4d5c1fdebe306f4e891ddfc4d3045a622d2f01db21ecfc9397cab25c9baa91a
  - name: memcached-rhel7
    image: registry.redhat.io/3scale-amp2/memcached-rhel7@sha256:ff5f3d2d131631d5db8985a5855ff4607e91f0aa86d07dafdcec4f7da13c9e05
  - name: redis-32-rhel7
    value: registry.redhat.io/rhscl/redis-32-rhel7@sha256:a9bdf52384a222635efc0284db47d12fbde8c3d0fcb66517ba8eefad1d4e9dc9
  - name: mysql-57-rhel7
    value: registry.redhat.io/rhscl/mysql-57-rhel7@sha256:9a781abe7581cc141e14a7e404ec34125b3e89c008b14f4e7b41e094fd3049fe
  - name: postgresql-10-rhel7
    value: registry.redhat.io/rhscl/postgresql-10-rhel7@sha256:de3ab628b403dc5eed986a7f392c34687bddafee7bdfccfd65cecf137ade3dfd
...

可以看到 relatedImages 列表中有些條目的鍵是 value 而不是 image,這就是問題所在! 那些沒有 image 的條目在反序列化時會將 image 的值當成空字符串 "":

$ sqlite3 /tmp/db-225652515/bundles.db 'select * from related_image where operatorbundle_name="3scale-operator.v0.5.0"'

registry.redhat.io/3scale-amp2/zync-rhel7@sha256:f4d5c1fdebe306f4e891ddfc4d3045a622d2f01db21ecfc9397cab25c9baa91a|3scale-operator.v0.5.0
registry.redhat.io/3scale-amp2/memcached-rhel7@sha256:ff5f3d2d131631d5db8985a5855ff4607e91f0aa86d07dafdcec4f7da13c9e05|3scale-operator.v0.5.0
|3scale-operator.v0.5.0
registry.redhat.io/3scale-amp2/apicast-gateway-rhel8@sha256:21be62a6557846337dc0cf764be63442718fab03b95c198a301363886a9e74f9|3scale-operator.v0.5.0
registry.redhat.io/3scale-amp2/backend-rhel7@sha256:ea8a31345d3c2a56b02998b019db2e17f61eeaa26790a07962d5e3b66032d8e5|3scale-operator.v0.5.0
registry.redhat.io/3scale-amp2/3scale-rhel7-operator@sha256:2ba16314ee046b3c3814fe4e356b728da6853743bd72f8651e1a338e8bbf4f81|3scale-operator.v0.5.0
registry.redhat.io/3scale-amp2/system-rhel7@sha256:93819c324831353bb8f7cb6e9910694b88609c3a20d4c1b9a22d9c2bbfbad16f|3scale-operator.v0.5.0

從上面的輸出可以看到鍵為 value 的那幾個條目都反序列化失敗了,具體的討論參考:bundle validate should validate that there are no empty relatedImages。

這裏給出一個臨時解決方案,先打開另外一個窗口,然後回到原來的窗口執行命令:

$ oc adm catalog mirror \
  -a ${LOCAL_SECRET_JSON} \
  registry.openshift4.example.com/devinfra/redhat-operators:v1 \
  registry.openshift4.example.com

然後迅速切到下一個窗口,查找最新的 manifest 緩存目錄:

$ ls -l /tmp/cache-*/

根據日期判斷最新的緩存目錄,假設是 /tmp/cache-320634009,然後將所有的 value 替換為 image:

$ sed -i "s/value: registry/image: registry/g" $(egrep -rl "value: registry" /tmp/cache-320634009/)

同步完成後會產生 redhat-operators-manifests 目錄,下面有兩個文件:

  • imageContentSourcePolicy.yaml : 定義了一個 ImageContentSourcePolicy 對象,該對象可以配置節點將其對官方 Operator manifests 中鏡像的引用改為對本地鏡像倉庫中鏡像的引用。
  • mapping.txt : 包含了所有的源鏡像在本地鏡像倉庫中的映射位置。oc image mirror 命令可以引用該文件進一步修改鏡像配置。

然而目前這麼做還是有問題 1800674: 同步出來的鏡像 manifest digest 不對,導致後面離線安裝 Operator 時會報鏡像無法獲取的錯誤。

暫時可以使用上面 bugzilla 鏈接里給出的臨時解決方案,先安裝 skopeo:

$ yum install -y golang gpgme-devel libassuan-devel btrfs-progs-devel device-mapper-devel
$ git clone https://github.com/containers/skopeo
$ cd skopeo
$ make binary-local
$ mv skopeo /usr/local/bin/

從 pull-secret.json 中解碼 quay.io、registry.redhat.io 和 registry.access.redhat.com 的用戶名密碼,然後通過下面的命令認證:

$ skopeo login -u <quay.io_user> -p <quay.io_psw> quay.io
$ skopeo login -u <registry.redhat.io_user> -p <registry.redhat.io_psw> registry.redhat.io
$ skopeo login -u <registry.access.redhat.com_user> -p <registry.access.redhat.com_psw> registry.access.redhat.com

最後同步鏡像的 manifest digest:

cat redhat-operators-manifests/mapping.txt | while read line; do
  origin=$(echo $line | cut -d= -f1)
  target=$(echo $line | cut -d= -f2)
  if [[ "$origin" =~ "sha256" ]]; then
    tag=$(echo $origin | cut -d: -f2 | cut -c -8)
    skopeo copy --all docker://$origin docker://$target:$tag
  else
    skopeo copy --all docker://$origin docker://$target
  fi
done

不得不說,OCP 的安裝真是個浩大的工程,這洋洋洒洒的一大篇也只是準備了離線資源,這隻是安裝的一小步,還有很長的步驟要寫,心理素質不過關的同學切勿隨意模仿。

5. 參考資料

  • 離線部署 Openshift Container Platform 4.3 – 1: 準備離線資源
  • Chapter 9. Using Operator Lifecycle Manager on restricted networks

Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

※網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

深度學習在高德ETA應用的探索與實踐

1.導讀

駕車導航是数字地圖的核心用戶場景,用戶在進行導航規劃時,高德地圖會提供給用戶3條路線選擇,由用戶根據自身情況來決定按照哪條路線行駛。

同時各路線的ETA(estimated time of arrival,預估到達時間)會直接显示給用戶,這是用戶關心的核心點之一。用戶給定起點和終點后,我們的任務是預測起終點的ETA,ETA的準確率越高,給用戶帶來的出行體驗越好。

2.基於深度學習模型的探索和實踐

2.1模型選擇

傳統機器學習模型在ETA中,比較常用的有線性回歸、RF(隨機森林)、GBDT(梯度提升決策樹)等回歸預測類模型。線性模型表達能力較差,需要大量特徵工程預先分析出有效的特徵;RF通過樣本隨機和特徵隨機的方式引入更多的隨機性,解決了決策樹泛化能力弱的問題;GBDT是通過採用加法模型(即基函數的線性組合),以及不斷減小訓練過程產生的殘差來達到回歸的算法。

傳統機器學習模型相對簡單易懂,也能達到不錯的效果,但存在兩個問題:

  • 模型的表達能力跟選取的特徵有關,需要人工事先分析出有效的特徵。
  • 沒有考慮上游對下游路段的影響,產生了如丟失上下游關聯信息、下游受上游影響導致的不確定性等問題。

第一個問題很好理解,深度學習模型能很好地彌補這方面。針對第二個問題,以歷史速度信息選取存在的不確定性為例來說明一下,歷史速度信息是一個區分周一到周日七個工作日、10分鐘間隔的歷史平均時間,可以根據該路段的預計進入時間所在10分鐘區間來選定。如下圖(歷史平均速度)從0:00-24:00的變化曲線,可以看到一天中特別是早晚高峰,速度值存在較大波動。

而在選取歷史平均時間時,依賴的是預計進入時間,這個時間依賴於上游路段的預計通行時間,因此其選取存在不確定性,進而導致ETA計算不準確。

考慮到以上問題的存在,我們選擇利用RNN的時間序列思想將路線中上下游路段串聯起來進行路段ETA的預測。

另外考慮到RNN存在的長依賴問題,且結合實際業務情況,我們選擇使用LSTM模型來進行建模,LSTM的門結構具有的選擇性還能讓模型自行學習選擇保留哪些上游的特徵信息進行預測。

2.2網絡架構

上圖為整個模型的框架圖,主要分為兩部分,使用LSTM模塊對路線中的路段ETA的預測和最終使用N層全連接模塊對累計路段ETA及路線各特徵進行完整路線的ETA預測。

2.3路段ETA預測

上圖為各路段ETA預測使用的LSTM結構圖,Xt為路線中第t個路段的特徵信息,主要包含對應的實時路況信息、歷史路況信息、路段的靜態特徵等。

LSTM本是輸入時間序列數據的模型,我們利用該思想,將路線中各路段序列依次輸入模型。

2.4完整路線ETA預測

在LSTM模塊得到累計路線ETA預測值后,結合該路線的靜態屬性,使用全連接模塊將其整合成最終輸出的完整路線ETA預測值。

路線的屬性特徵主要指一些人工提取的特徵,如該路線的長度、導航規劃發起特徵日、是否早晚高峰時段等,用以加強模型在不同場景下的表達能力。

損失函數選用線性回歸常用的平方形式:MSE,公式如下:

其中,N是路線數量,ETA路線j為路線ETA,即預測值;用戶實走j為用戶在該路線的實走時間,即真值。

3.模型效果

衡量模型效果,即路線上ETA的預測值時,主要考慮的是準確率。一般情況下,用戶對ETA偏長和偏短的容忍度不同,對偏長容忍度更高。比如用戶要去機場,ETA給的時間偏短10分鐘比偏長10分鐘對用戶的損害更大。因此準確度的指標設計傾向於ETA偏長,定義為滿足用戶一定容忍範圍的請求比例,即準確率作為主要衡量指標。

在北京市上的實驗結果显示,ETA準確率得到提升,MSE loss下降比例28.2%,效果有了明顯的提升。

4.小結

本文介紹了引入深度學習模型,幫助建模導航規劃的預估到達時間預測,成功解決了線性模型的不足,也為後續引入更多特徵、進行更多探索打開了空間,如歷史速度信息的不確定度、時效性、周期性、突發事件、路網結構等。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價