為什麼汽車駕駛位不能放在正中間?

不正經回答:1。我們國內的交通規則遵循的是右側通行原則,即車輛行人靠道路右側通行,這種情況下,駕駛座設在左側便於駕駛員觀察對面來車情況,特別是會車和超車的時候,所以右側超車這種事情就不要搞了。為什麼主駕駛在左邊呢。

前段時間港珠澳大橋的完工引來了不少話題,比如沒有連通深圳就是然並卵,拋開那些有用論無用論不談,其中一點值得一說,內陸的車都是左舵,靠右行駛,而港澳地區則是右舵車,靠左行駛,通車以後的大橋上,靠左行駛還是靠右行駛好像也是個問題,那麼為什麼汽車的駕駛席會有左右之分呢?為什麼駕駛席不放在中間呢?由於很多答案都沒有歷史考證,所以以下回答分為兩種。

正經回答

1.為什麼英國靠左行駛?如今大多數國家和地區都是遵循右側通行原則,但是英國及其殖民地、附屬國是採用左側通行原則,駕駛座是設在車輛的右側的,據說英國的這種交通規則是源於中世紀英國騎士是右手持矛,靠左側通行是為了防備被襲擊,要將對面來的人暴露在便於攻擊的右側。香港因為曾經是英國殖民地,到現在為止還保留左側通行的習慣。

2.英國靠左行駛有那麼講究的故事,那麼美國靠右行駛又有什麼原因呢,在早期的時候,汽車的輪胎質量不好,如果把車停在路的左側,司機上車前就要繞車一周檢查右面輪胎是否有氣。(話說現在還有多少有這個好習慣?)

許多司機為了方便,就把車右側順停在了路的右側,右側輪胎一出家門就看到了,上車時也就順便看到了左邊輪胎。 同時,那時的司機大都是職業人士,為了方便主人和客人的上下車方便,都樂意右停。所以就“習慣成自然了”。

3.為什麼主駕駛非要在兩邊而不是放中間?說到駕駛席放中間的除了老爺車我們就會想到邁凱倫F1座位的布局,主駕駛在中間,左右各還有一個乘客的位置,其實早在1966年,法拉利的365 p Berlinetta Speciale就已經採用過如此布局。

對於駕駛者來說這樣確實能夠有更直接的視角,和F1賽車一樣,但是在公路行駛的時候則會面臨一定的尷尬。比如邁凱倫F1就有兩個內后視鏡,就是因為視線不佳。而且超車的時候比較困難,要往外探得更多才能看到路況。

不正經回答:

1.我們國內的交通規則遵循的是右側通行原則,即車輛行人靠道路右側通行,這種情況下,駕駛座設在左側便於駕駛員觀察對面來車情況,特別是會車和超車的時候,所以右側超車這種事情就不要搞了。為什麼主駕駛在左邊呢?這是因為不能犯右的錯誤。

2.坐中間的話不方便往外吐痰,如果肺活量不夠很容易吐到車內。

3.坐中間抽煙的話手沒有地方搭。(手不夠長)

4.最早的汽車是沒有轉向燈的,要把手伸出窗外打手勢,如果主駕駛放中間的話實在是夠不到啊。

5.要是駕駛位在正中間,你想讓教練坐哪邊呢?

6.英國人靠右邊是因為Right(右邊)就是Right(正確)的位置。

其實“習慣成自然”這句話能夠解決很多問題,就像我們現在熟用QWERTY鍵盤其實是效率比不上日後發明的dvorak和Colemak布局。

QWERT鍵盤

dvorak鍵盤布局。

Colemak鍵盤布局。

是不看了另外的布局瞬間就覺得不會打字了,所以因為習慣問題也沒多少人會去用。所以不管是靠左行駛還是靠右行駛都有自己解決問題的辦法,駕駛席都放在了靠超車的一邊,不過一想到右舵車是用左手換擋總覺得很怪,畢竟右手是好不容易練就的麒麟臂。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

這年頭,連英特爾都來搞汽車了……

我們組建的專項團隊擁有清晰的共同目標,以及創新、敏捷且負責任的文化。”Mobileye聯合創始人、董事長兼首席技術官Amnon Shashua表示:“為高速路和普通城市道路設計一流的全自動駕駛解決方案,我們在過去六個月里取得了重大進展。

(拉斯維加斯/北京)2017年1月4日,寶馬集團、英特爾和Mobileye宣布大約40輛寶馬全自動駕駛汽車將於2017年下半年開始路測。這是三家公司攜手全面實現全自動駕駛目標所邁出的重要一步。在CES期間的聯合新聞發布會上,三家公司進一步披露,這些BMW 7系測試車輛將採用英特爾和Mobileye的尖端技術,從美國和歐洲開始全球路測之旅。

此次發布是寶馬集團、英特爾和Mobileye於去年7月宣布合作后,首次披露全自動駕駛項目進展。從那時起,三家公司就在着手開發一個可拓展架構。其他汽車開發商和生產商可以在此平台上實現一流的設計,並打造差異化品牌。這些產品從單個關鍵集成模塊到完整的端到端解決方案,提供各種差異化的消費者體驗。”

寶馬集團負責研發的董事傅樂希先生表示:“讓全自動駕駛成為現實,這是寶馬、英特爾與Mobileye的共同目標。此次合作彙集了所有必要的技能和人才,以克服未來的無數技術挑戰,並實現全自動駕駛汽車的商業化。因此,我們已經考慮到了可擴展性,並歡迎製造商、供應商或科技公司參与其中,共同為全自動駕駛平台做出貢獻。今年,我們的測試將在全球各地上路實測這項聯合技術。對於我們的目標——在2021年推出寶馬集團第一款全自動駕駛汽車BMW iNEXT,這將是重要的一步。”

英特爾公司首席執行官科再奇表示:“站在行業的角度,通過分擔開發成本並整合資源開發全自動駕駛平台,我們實現了成本的節約和開發速度的提升。這個從汽車到雲的系統將擁有連貫且可預測的行為,並將遵循最高的安全標準運行。這就是為什麼此次合作堪稱破冰之舉。我們組建的專項團隊擁有清晰的共同目標,以及創新、敏捷且負責任的文化。”

Mobileye聯合創始人、董事長兼首席技術官Amnon Shashua表示:“為高速路和普通城市道路設計一流的全自動駕駛解決方案,我們在過去六個月里取得了重大進展。這是一個可拓展的解決方案,能夠滿足參与該項目的汽車製造商的獨特需求。”

在此次合作中,寶馬集團將負責駕駛控制和動態表現、整體功能的安全性評估、模擬高性能發動機的設置、整體組件集成、原型車生產以及最終通過合作夥伴部署實現平台的擴展。

英特爾所帶來的創新的高性能計算能力,從汽車一直覆蓋到數據中心。全新發布的英特爾® GO™全自動駕駛解決方案提供世界級處理器和FpGA技術,以最高效的方式平衡了性能和功耗,同時滿足汽車行業對散熱和安全性的苛刻要求。在汽車內,英特爾GO解決方案為關鍵功能提供了可拓展的開發和計算平台,包括傳感器聚合、駕駛策略、環境建模、路徑規劃和決策。在數據中心內,英特爾GO提供廣泛的技術,從高性能英特爾®至強®處理器,到英特爾® Arria® 10 FpGAs和英特爾®固態硬盤,到英特爾® Nervana™人工智能平台等,為全自動駕駛行業所需的機器學習和深度學習,提供了強大的訓練和模擬基礎設施。

Mobileye則貢獻了其專有的EyeQ®5高性能計算機視覺處理器,提供汽車級功能安全和低功耗性能。EyeQ®5負責處理和解構來自360度全景視覺傳感器的信息,以及本地化處理。EyeQ®5結合英特爾CpU和FpGA技術構成的中央計算平台,將集成到每一輛全自動駕駛汽車中。

Mobileye計劃與寶馬集團進一步合作,共同開發傳感器融合解決方案,利用來自視覺、雷達和激光雷達傳感器的數據,創建一個完整的車輛周邊環境模型。雙方還將制定駕駛策略,包括Mobileye的增強學習算法,這將賦予汽車系統所需的人工智能,在複雜的駕駛情況下進行安全溝通。

為了進一步推動全自動駕駛平台的開發,三家公司計劃在未來幾年陸續發布硬件樣品和軟件更新。計劃於2021年發布的BMW iNEXT車型將是寶馬集團全自動駕駛戰略的基石。在這款汽車之後,寶馬集團旗下各品牌也將推出高度自動化的車型。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

格陵蘭融冰量破紀錄 海平面加速上升拉警報

摘錄自2020年8月23日中央社報導

根據最新研究,格陵蘭冰蓋去年淨減少5320億公噸的冰,打破歷來最高紀錄,相當於全球海洋每天注入額外300萬噸水。

今天(23日)發表在「地球與環境通訊」(Communications Earth & Environment)期刊的研究指出,從格陵蘭(Greenland)崩離的冰層和融冰洪流,是造成去年全球海平面上升的單一最大來源,占所有增加水量的40%。格陵蘭去年流失的冰量,比2012年創造的最高紀錄至少多了15%。

研究的第一作者冰河學家薩斯根(Ingo Sasgen)告訴法新社:「史上格陵蘭冰蓋全年減少規模創新高的五次,全都發生在過去十年內。」

若格陵蘭冰蓋全部融化,全球海平面將升高達7公尺。就算海平面上升幅度沒這麼高,也會重劃全球海岸線,讓數億人現居的土地不再宜居。

海洋
氣候變遷
國際新聞
格陵蘭
海平面上升
融冰
冰蓋

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

想開豪車又有新選擇?金杯蒂阿茲正式上市

整體外觀大氣,但原創度不高。前臉盾型進氣格柵造型醒目,日行燈帶及柳恭弘=叶 恭弘型大燈讓車頭造型頗具張力。車身側麵線條較為簡單,幸好鯊魚鰭天線、行李架及后擾流板挽回了不少視線。至於車尾,除了保險杠的造型以外,其餘原創度不高。

2017年1月7日,華晨華瑞以“天生大智 親民豪車”為主題,在成都東郊記憶演藝中心,舉行了金杯蒂阿茲的全球上市發布會。作為華晨華瑞的首款SUV車型,蒂阿茲搭載1.5T+6MT的動力組合,長寬高分別為4745/1856/1710mm,軸距為2725mm,將成為自主SUV車型的又一員猛將。

此次蒂阿茲上市,帶來了4款1.5T車型,價格區間為7.98~9.88萬元:

1.5T 6MT潮流版,售價為7.98萬;

1.5T 6MT時尚版,售價為8.88萬;

1.5T 6MT卓越版,售價為9.38萬;

1.5T 6MT領航版,售價為9.88萬。

外觀大氣 原創度不高

蒂阿茲的外觀設計,借鑒了不少謳歌MDX的設計元素。整體外觀大氣,但原創度不高。前臉盾型進氣格柵造型醒目,日行燈帶及柳恭弘=叶 恭弘型大燈讓車頭造型頗具張力。

車身側麵線條較為簡單,幸好鯊魚鰭天線、行李架及后擾流板挽回了不少視線;至於車尾,除了保險杠的造型以外,其餘原創度不高。

空間寬闊 暫無提供7座版本

蒂阿茲車身尺寸為4745mm/1856mm/1710mm,軸距達2725mm,使得面對一系列自主緊湊型SUV競爭對手時不落下風。後備箱容積達1002L,放倒第二排座椅后,後備箱空間最高達到1845L。

黑棕雙色內飾,為平淡的內飾增添了一絲豪華感;此外,蒂阿茲配備0.92㎡的超大天窗,為車內採光提供不少幫助。

1.5T+6MT 配合四輪獨立懸挂

蒂阿茲搭載4A15TS發動機,可輸出113Kw/220Nm峰值功率與扭矩,搭配6擋手動變速箱。暫無提供自動變速箱車型,對市場佔有率有一定影響。

蒂阿茲的亮點之一,配備四輪獨立懸架,並採用了一部分鋁合金輕質材料,配合18寸輪圈,對視覺及操控有一定幫助。

搭載carlife系統 安全配置充足

為滿足80/90後主流消費群體的需求,蒂阿茲配備Carlife系統及9英寸觸控屏,並對NVH作出針對性的研發。

此外GpS導航、360°全景環視,自動感應大燈,感應雨刮,自動泊車輔助等豪華配置外,還增配了ABS+EBD+BA剎車輔助,胎壓監測、等安全配置都能在蒂阿茲上找到,重視安全值得肯定。

親民豪車 符合市場需求

伴隨SUV銷量持續井噴,開發迎合消費者需求的親民豪車無可厚非。華晨華瑞乘用車公司副總經理魏東表示:蒂阿茲作為華晨華瑞品牌力提升的旗艦車型,有望躋身自主品牌SUV主流陣營。在競爭激烈的中國自主品牌汽車市場,蒂阿茲將以“親民豪車”的姿態,樹立行業新標杆。

總結:蒂阿茲為關注SUV的購車者帶來全新選擇。安全配置豐富、空間超越同級,擁有着親民豪車的本領,可惜外觀原創度不高是一大弊端。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

依舊高級感十足,為什麼教授會更喜歡這款11萬的手動擋RX5?

親自感受了一番榮威RX5的6MT之後,想用一個字去形容——爽,首先是換擋行程短,換擋時候手臂動作不大,其次是掛擋手感非常乾脆,不會有那種油膩的黏黏糊糊的感覺。離合器的腳感很輕,不會是很彈腳的那種,這種設定對於經常在市區走走停停的路況會比較好,確實能有效減輕左腳的負擔。

有些事情總是在不知不覺中發生,就在幾年前,大多數消費者的購車預算還停留在十萬元內,不管是國產車還是合資車,大家選擇的大多都是手動擋車型,然而到了2017的今天,不知不覺中手動擋已經淡出我們的視線。

應該有不少人都覺得現在買個手動擋就是掉檔次,和親戚朋友聊天時問起都不好意思說出來,但是試駕完這台SUV界網紅——榮威RX5 20T 手動旗艦版,真心覺得手動擋也很有面子。

外觀與高配車型差別不大,很良心

大家先不要着急,好戲在後頭,雖然大家都已經對榮威RX5非常熟悉,但還是要先來回顧一下它的外觀內飾,因為前不久剛剛試過了榮威RX5的30T車型,所以對這次試駕的榮威RX5 20T 手動旗艦版有一些獨特的見解。

先來看看外觀上最大的“亮點”,這次試駕的20T 手動旗艦版配備了遠近光一體帶透鏡鹵素大燈,大燈燈組內集成了燈帶式的LED日間行車燈,造型相當好看,非常亮眼,順帶提一句,20T 手動旗艦版可以選裝全LED大燈,效果非常驚艷,強烈推薦准車主選裝這個配置。

作為整個RX5車系中的中低配車型,全LED大燈需要選裝其實很正常,如果你覺得稍有遺憾的話,不要緊,因為那個吸取了小提琴元素,點亮后辨識度非常高的尾燈還是在的,每次看到RX5都會忍不住看多兩眼。

榮威RX5能夠在短時間內突破兩萬輛的銷量,其“律於形而動於心”的律動設計理念有很大的功勞,車身多處使用了0.618的黃金分割比例,最終呈現出一個非常精悍的造型,這也是榮威RX5和競爭對手們的區別,一點都不顯得臃腫,而是精神秀氣。

內飾簡約大氣,中控操作便利

一坐進車內,那個互聯網車型獨有,並且讓無數消費者津津樂道的10.4英寸中控大屏不見了,取而代之的是一塊8英寸的屏幕,不要以為屏幕大就是好用,只能說大家都有各自的特點,20T 手動旗艦版配備的這個8英寸屏幕,功能齊全,導航、藍牙電話、Apple Carplay和百度CarLife等等一應俱全。

看到中控屏幕下方的空調操作面板,非常欣慰,如果你和一樣覺得在觸控屏上調節空調不方便的話,那麼你一定更加喜歡這個實體操作面板,熟悉之後能夠在行車當中進行盲操作,除此之外,整个中控台按鍵、旋鈕的裝配工藝和阻尼感都非常出色,在同級別當中處於領先水準。

好開順手,懸架支撐性強

終於到了最激動人心的部分,到底這個6擋手動變速箱的表現如何了,到底值不值得買呢?親自感受了一番榮威RX5的6MT之後,想用一個字去形容——爽,首先是換擋行程短,換擋時候手臂動作不大,其次是掛擋手感非常乾脆,不會有那種油膩的黏黏糊糊的感覺。

離合器的腳感很輕,不會是很彈腳的那種,這種設定對於經常在市區走走停停的路況會比較好,確實能有效減輕左腳的負擔。因為配備的1.5T發動機扭矩平台足夠低和寬廣,離合器和變速箱的結合匹配寬容度很高,都很容易上手,各個擋位間切換順滑。缺點也是有的,美中不足的地方是離合器的結合點模糊,起步時空擋掛入1擋鬆開離合過程中結合點不好找,一不留神就容易憋熄火,還好憑藉多年老司機臨床經驗,基本上靜下心來試個三兩次就好了。1.5T渦輪增壓發動機的表現也是很盡職,如果你第一次接觸它,不跟你說它是渦輪增壓發動機的話,你一定會以為是自然吸氣發動機。

發動機與渦輪的噪音控制讓感到驚喜,發動機轉速在2700轉內,發動機艙傳來的聲音不會打擾到乘客。另外發動機動力輸出平順,日常行車狀態下絕對夠用,得益於6擋手動變速箱,高速巡航時的燃油經濟性表現很好,100km/h時速下發動機轉速為2000轉,120km/h的時候是2500轉,儀錶盤還會根據當前擋位和踩油門的深度給出ECO節油指示。

榮威RX5的底盤懸架調校也是比較喜歡的,過彎時候的支撐性很好,側傾控制出色,讓駕駛員很有信心,與此同時,還能夠做到對路面的坑窪顛簸進行有效過濾,保證良好的舒適性,再加上方向盤轉向手感適中,整個駕駛體驗比的預想要好不少,感覺它的實力已經超出了它11.98萬的指導價,性價比高。

有話說

其實喜歡手動擋的大有人在,但是為什麼大家最後都選擇了自動擋車型,估計不是因為不顯檔次就是因為開起來不順手,雖然這次試駕的榮威RX5的20T 兩驅手動旗艦版還沒有達到完美,但是並不妨礙它成為一輛好車,一輛值得推薦的車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

過年回家好多人買了哈弗和眾泰,到底該不該也買一輛?

再舉個生動栗子,的朋友陳二狗是一個下水道維修人員,經過幾年的艱苦工作,終於存夠錢買車了,但是不知道買什麼車。然鵝看到了隔壁的老王買了一台新車,外觀挺好看的,坐進去看起來配置也不錯,問一下價格竟然還不貴。

春節回家過年,是我們中國人傳統的習俗。過年除了回家探望親人,最大的目的還是在“炫耀”上,中國人都存在一種攀比的心理,比誰混得更好是春節回家的一個必備項目。汽車是一件“奢飾品”,作為身份的象徵,不少人都會在過年前買一台小汽車,開回老家彰顯身份。

也正是有這樣的“攀比”心理,導致中國人買車變得極度不理性。說實話“沒主見”是最主要的因素之一。因為購車所需要考慮的因素很多,不管你對車輛熟不熟悉,到了真正購車時,購車前所有的準備都煙消雲散,到了4S店你也只會 一味地跟着銷售的套路走下去。

為什麼消費者的購車思想都不理性?

影響消費者買車的因素有很多,像車型的品牌、車型的顏值、車型的配置、汽車的售價/優惠幅度、提車時間等,另外消費者們還有一個最為致命的共通點:就是跟風消費。

舉個栗子,就像哈弗H6那樣,每個月都有幾萬的銷量,一年能賣上幾十萬台的車型,難道就真的這麼多人買?那是肯定的,就好比國家在發展,人們的生活質量日益提高,所以不少家庭都開始將購車的計劃提上了議程。相對於一線大城市,在汽車保有量方面已經趨向於飽和甚至是過剩的狀態,所以目前銷售主力主要集中在二三線甚至是更偏的城市,而其中絕大部分的人都是首次購車的消費者。

其實大部分人都是和一樣的普通人,如果不是專業做汽車,也不知道買什麼車才是最好的選擇。對於自己本身來說,大多都是靠自己的努力辛辛苦苦存錢買車,很重視汽車的價格以及優惠幅度。也可以這樣說,如果是比較極端的消費者,在15萬以內的購車預算,兩個相差不大的競品車型,也許是因為某個車型有更多的優惠、價格更低。於是選擇了這台車。

再舉個生動栗子,的朋友陳二狗是一個下水道維修人員,經過幾年的艱苦工作,終於存夠錢買車了,但是不知道買什麼車。然鵝看到了隔壁的老王買了一台新車,外觀挺好看的,坐進去看起來配置也不錯,問一下價格竟然還不貴。再過了两天同村的老李又買了輛一模一樣的車,哇!同村這麼多人都買這車,質量肯定可以了,於是二狗高高興興地又買了一輛,於是他們愉快地創辦了二貨村殺馬特車友會。

其實大家都一樣,往往容易被表面迷惑,買車大多是看外觀數配置,這也是目前眾多車型品牌在追求的一個東西,外觀越來越大氣、配置越來越高,價格也越來越低了。這也是為什麼能夠火了部分喜歡“山寨”的品牌,譬如最近新出的保時泰SR9,10多萬的售價竟然有百萬級SUV的外觀,不僅如此連內飾的設計都有極高的相似度。當然消費者對於保時泰SR9的熱情是非常高的,關鍵是逼格夠高。還得說一下,能不能開上蘭博基尼就得看眾泰了。

只不過買這些車的人在選擇的時候壓根就沒有想過這車的質量到底行不行,至於眾泰這個品牌的質量能不能過關?還是建議去看一下車主的口碑,不是說質量不行,只是車型整體故障率還是比較高的。不過這類山寨車型還有一個好處呀,我還能剩下一大筆的設計費呀!

看得見的地方給到你逼格十足的感覺,但是在看不到不知道的地方就給你各方面的減配,你覺得這樣真的好嗎?為了降低成本,同一個部位的零件,哪個供應商便宜選哪個,就算質量差一點也是沒所謂,這樣的行為見得太多了!當然,這樣的行為不僅出現在國產品牌中,更有出現在合資品牌上。不過厚道的汽車廠家不少,只是消費者們不懂得識別,這也是目前我國消費者在購車方面最大的囧況。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

線程池運用不當的一次線上事故

在高併發、異步化等場景,線程池的運用可以說無處不在。線程池從本質上來講,即通過空間換取時間,因為線程的創建和銷毀都是要消耗資源和時間的,對於大量使用線程的場景,使用池化管理可以延遲線程的銷毀,大大提高單個線程的復用能力,進一步提升整體性能。

今天遇到了一個比較典型的線上問題,剛好和線程池有關,另外涉及到死鎖、jstack命令的使用、JDK不同線程池的適合場景等知識點,同時整個調查思路可以借鑒,特此記錄和分享一下。

01 業務背景描述

該線上問題發生在廣告系統的核心扣費服務,首先簡單交代下大致的業務流程,方便理解問題。

綠框部分即扣費服務在廣告召回扣費流程中所處的位置,簡單理解:當用戶點擊一個廣告后,會從C端發起一次實時扣費請求(CPC,按點擊扣費模式),扣費服務則承接了該動作的核心業務邏輯:包括執行反作弊策略、創建扣費記錄、click日誌埋點等。

02 問題現象和業務影響

12月2號晚上11點左右,我們收到了一個線上告警通知:扣費服務的線程池任務隊列大小遠遠超出了設定閾值,而且隊列大小隨着時間推移還在持續變大。詳細告警內容如下:

相應的,我們的廣告指標:點擊數、收入等也出現了非常明顯的下滑,幾乎同時發出了業務告警通知。其中,點擊數指標對應的曲線表現如下:

該線上故障發生在流量高峰期,持續了將近30分鐘后才恢復正常。

03 問題調查和事故解決過程

下面詳細說下整個事故的調查和分析過程。

第1步:收到線程池任務隊列的告警后,我們第一時間查看了扣費服務各個維度的實時數據:包括服務調用量、超時量、錯誤日誌、JVM監控,均未發現異常。

第2步:然後進一步排查了扣費服務依賴的存儲資源(mysql、redis、mq),外部服務,發現了事故期間存在大量的數據庫慢查詢。

上述慢查詢來自於事故期間一個剛上線的大數據抽取任務,從扣費服務的mysql數據庫中大批量併發抽取數據到hive表。因為扣費流程也涉及到寫mysql,猜測這個時候mysql的所有讀寫性能都受到了影響,果然進一步發現insert操作的耗時也遠遠大於正常時期。

第3步:我們猜測數據庫慢查詢影響了扣費流程的性能,從而造成了任務隊列的積壓,所以決定立馬暫定大數據抽取任務。但是很奇怪:停止抽取任務后,數據庫的insert性能恢復到正常水平了,但是阻塞隊列大小仍然還在持續增大,告警並未消失。

第4步:考慮廣告收入還在持續大幅度下跌,進一步分析代碼需要比較長的時間,所以決定立即重啟服務看看有沒有效果。為了保留事故現場,我們保留了一台服務器未做重啟,只是把這台機器從服務管理平台摘掉了,這樣它不會接收到新的扣費請求。

果然重啟服務的殺手鐧很管用,各項業務指標都恢復正常了,告警也沒有再出現。至此,整個線上故障得到解決,持續了大概30分鐘。

04 問題根本原因的分析過程

下面再詳細說下事故根本原因的分析過程。

第1步:第二天上班后,我們猜測那台保留了事故現場的服務器,隊列中積壓的任務應該都被線程池處理掉了,所以嘗試把這台服務器再次掛載上去驗證下我們的猜測,結果和預期完全相反,積壓的任務仍然都在,而且隨着新請求進來,系統告警立刻再次出現了,所以又馬上把這台服務器摘了下來。

第2步:線程池積壓的幾千個任務,經過1個晚上都沒被線程池處理掉,我們猜測應該存在死鎖情況。所以打算通過jstack命令dump線程快照做下詳細分析。

#找到扣費服務的進程號
$ ps aux|grep "adclick"

# 通過進程號dump線程快照,輸出到文件中
$ jstack pid > /tmp/stack.txth

在jstack的日誌文件中,立馬發現了:用於扣費的業務線程池的所有線程都處於waiting狀態,線程全部卡在了截圖中紅框部分對應的代碼行上,這行代碼調用了countDownLatch的await()方法,即等待計數器變為0后釋放共享鎖。

第3步:找到上述異常后,距離找到根本原因就很接近了,我們回到代碼中繼續調查,首先看了下業務代碼中使用了newFixedThreadPool線程池,核心線程數設置為25。針對newFixedThreadPool,JDK文檔的說明如下:

創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。如果在所有線程處於活躍狀態時提交新任務,則在有可用線程之前,新任務將在隊列中等待。

關於newFixedThreadPool,核心包括兩點:

1、最大線程數 = 核心線程數,當所有核心線程都在處理任務時,新進來的任務會提交到任務隊列中等待;

2、使用了無界隊列:提交給線程池的任務隊列是不限制大小的,如果任務被阻塞或者處理變慢,那麼顯然隊列會越來越大。

所以,進一步結論是:核心線程全部死鎖,新進的任務不對湧入無界隊列,導致任務隊列不斷增加。

第4步:到底是什麼原因導致的死鎖,我們再次回到jstack日誌文件中提示的那行代碼做進一步分析。下面是我簡化過後的示例代碼:

/*** 執行扣費任務 */
public Result<Integer> executeDeduct(ChargeInputDTO chargeInput) {  
    ChargeTask chargeTask = new ChargeTask(chargeInput);  
    bizThreadPool.execute(() -> chargeTaskBll.execute(chargeTask ));  
    return Result.success();
}

/*** 扣費任務的具體業務邏輯 */
public class ChargeTaskBll implements Runnable {  
    public void execute(ChargeTask chargeTask) {     
        // 第一步:參數校驗     
        verifyInputParam(chargeTask);     

        // 第二步:執行反作弊子任務     
        executeUserSpam(SpamHelper.userConfigs);     

        // 第三步:執行扣費     
        handlePay(chargeTask);     

        // 其他步驟:點擊埋點等     ...  
    }
}

/*** 執行反作弊子任務 */
public void executeUserSpam(List<SpamUserConfigDO> configs) {  
    if (CollectionUtils.isEmpty(configs)) {     
        return;  
    }  try {    
        CountDownLatch latch = new CountDownLatch(configs.size());    
        for (SpamUserConfigDO config : configs) {      
           UserSpamTask task = new UserSpamTask(config,latch);      
           bizThreadPool.execute(task);    
        }    
        latch.await();  
    } catch (Exception ex) {    
        logger.error("", ex);  
    }
}

通過上述代碼,大家能否發現死鎖是怎麼發生的呢?根本原因在於:一次扣費行為屬於父任務,同時它又包含了多次子任務:子任務用於并行執行反作弊策略,而父任務和子任務使用的是同一個業務線程池。當線程池中全部都是執行中的父任務時,並且所有父任務都存在子任務未執行完,這樣就會發生死鎖。下面通過1張圖再來直觀地看下死鎖的情況:

假設核心線程數是2,目前正在執行扣費父任務1和2。另外,反作弊子任務1執行完了,反作弊子任務2和4都積壓在任務隊列中等待被調度。因為反作弊子任務2和4沒執行完,所以扣費父任務1和2都不可能執行完成,這樣就發生了死鎖,核心線程永遠不可能釋放,從而造成任務隊列不斷增大,直到程序OOM crash。

死鎖原因清楚后,還有個疑問:上述代碼在線上運行很長時間了,為什麼現在才暴露出問題呢?另外跟數據庫慢查詢到底有沒有直接關聯呢?

暫時我們還沒有復現證實,但是可以推斷出:上述代碼一定存在死鎖的概率,尤其在高併發或者任務處理變慢的情況下,概率會大大增加。數據庫慢查詢應該就是導致此次事故出現的導火索。

05 解決方案

弄清楚根本原因后,最簡單的解決方案就是:增加一個新的業務線程池,用來隔離父子任務,現有的線程池只用來處理扣費任務,新的線程池用來處理反作弊任務。這樣就可以徹底避免死鎖的情況了。

06 問題總結

回顧事故的解決過程以及扣費的技術方案,存在以下幾點待繼續優化:

1、使用固定線程數的線程池存在OOM風險,在阿里巴巴Java開發手冊中也明確指出,而且用的詞是『不允許』使用Executors創建線程池。 而是通過ThreadPoolExecutor去創建,這樣讓寫的同學能更加明確線程池的運行規則和核心參數設置,規避資源耗盡的風險。

2、廣告的扣費場景是一個異步過程,通過線程池或者MQ來實現異步化處理都是可選的方案。另外,極個別的點擊請求丟失不扣費從業務上是允許的,但是大批量的請求丟棄不處理且沒有補償方案是不允許的。後續採用有界隊列后,拒絕策略可以考慮發送MQ做重試處理。— 結束 —

– End –

作者簡介:程序員,985碩士,前亞馬遜Java工程師,現58轉轉技術總監。持續分享技術和管理方向的文章。如果感興趣,可微信掃描下面的二維碼關注我的公眾號:『IT人的職場進階』

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

博客園自用主題美化 – Light

事件背景

 

之前做過幾個主題,但是都有些看膩了,然後就想着啥時候重構一下,於是就有了現在的新樣式:

https://github.com/KU4NG/CNBLOG-Theme-Light

當然,如果你對之前的感興趣,也可以查看之前的分享:

https://github.com/KU4NG/CNBlog-Theme

裡面包含幾個主題,但是我是有些懶得維護了,太忙了,感興趣的自己去改改 CSS 就行了!

 

 

新主題配置方法

 

1. 基礎配置:

前往博客園設置頁面,就行配置,需要注意的是:

a. 博客標題就是最終你博客的 logo,所以選你喜歡的。

b. 我博客標題中的來自 windows 10 系統輸入法旁邊的表情。

c. 基礎主題選:SimpleMemory,這一步很重要,因為我得樣式都是根據該主題的 HTML 結構寫的。

 

2. 配置樣式:

將本文中提供的 CSS 代碼或者 github 上面 css 目錄下 style.css 文件中的內容粘貼到此處。

*{margin:0;padding:0;font-weight:normal;letter-spacing:1.5px;font-family:PingFangSC-Regular,HelveticaNeue-Light,'Helvetica Neue Light','Microsoft YaHei',sans-serif,Simsun}div{border-radius:0!important}a{text-decoration:none!important}a:link{text-decoration:none!important}a:visited{text-decoration:none!important}a:hover{text-decoration:none!important}a:active{text-decoration:none!important}body{background-color:#eee}#header{min-width:1200px;height:50px;width:100%;display:block;background-color:white;box-shadow:0 1px 2px 0 rgba(0,0,0,.05)}#header *{color:#333!important}#header h1{float:left;width:200px;padding-left:20px}#header h1 a{height:46px;line-height:46px;font-size:18px!important;font-weight:bolder!important;letter-spacing:3px!important}#header #lnkBlogLogo{display:none}#header h2{display:none}#header li{list-style:none;float:right;height:50px;line-height:50px;position:relative}#header li:hover{cursor:pointer}#header li a{font-size:12px;letter-spacing:2px;padding:0 20px}#header li a::before,#header li a::after{position:absolute;top:0;right:0;bottom:0;left:0;content:'';opacity:0;pointer-events:none;-webkit-transition:opacity .4s,-webkit-transform .4s;transition:opacity .4s,transform .4s}#header li a::before{border-top:2px solid #333;-webkit-transform:scale(0,1);transform:scale(0,1)}#header li:hover a::before,#header li:hover a::after{opacity:1;-webkit-transform:scale(1);transform:scale(1)}#header .blogStats{display:none}#header #navigator{margin-right:200px}#sidebar_search{display:block;width:200px;position:absolute;top:0;right:0;height:48px;line-height:48px}#sidebar_search h3{display:none}#sidebar_search .div_my_zzk{margin-top:0;margin-bottom:0}#sidebar_search .input_my_zzk{display:inline-block;vertical-align:middle;padding:0 10px;border:0;cursor:text;font-size:12px}#sidebar_search .btn_my_zzk{background-color:#009688;color:#fff!important;white-space:nowrap;text-align:center;cursor:pointer;padding-left:10px;padding-right:10px}#sidebar_search input:focus{outline:0}#sidebar_search input{outline:0;border:0}#main{width:1200px;margin:10px auto}#main #mainContent{width:960px;background-color:white;padding:20px;float:left}#main #mainContent .day{position:relative;border:1px dashed lightgrey;padding:15px;margin:0 0 15px 0}#main #mainContent .day .dayTitle{position:absolute;top:-1px;right:-1px;background-color:#eee;width:110px;text-align:right;padding:0 10px 3px 10px}#main #mainContent .day .dayTitle a{font-size:12px;color:#333;opacity:.6}#main #mainContent .day .postTitle a{position:relative;left:-18px;border-left:4px solid #dc3545;padding-left:20px;font-size:18px;font-weight:bolder!important;color:#333}#main .postTitle span{font-weight:bolder!important}#main #mainContent .day .postTitle a:hover{color:#036}#main #mainContent .day .postCon{padding:10px}#main #mainContent .day .postCon .c_b_p_desc{font-size:12px;letter-spacing:2px;opacity:.6;line-height:2;width:900px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#main #mainContent .day .postDesc{font-size:12px;opacity:.6;text-align:right}#main #mainContent .day .postDesc a{font-size:12px;color:#dc3545}#main #mainContent .day .postSeparator{height:15px;border-top:1px dashed lightgrey;border-bottom:1px dashed lightgrey;border-left:2px solid white;border-right:2px solid white;margin:15px -17px}#main #mainContent #nav_next_page a{font-size:12px;color:#333}#main #mainContent .topicListFooter{margin-right:0}#main #mainContent .pager{font-size:12px;color:#dc3545;text-align:right}#main #mainContent .pager a{font-size:12px;color:gray;border:1px solid lightgray}#main #mainContent #homepage_top_pager{display:none}#sideBar{width:200px;float:right}#sideBar #sideBarMain{width:190px;float:right}#sideBar #sideBarMain *{color:#333;font-size:12px;letter-spacing:2px}#sideBar #sideBarMain #sidebar_news{background-color:white}#sideBar #sideBarMain #sidebar_news h3{background-color:#dc3545;color:white;border-bottom:1px solid #eee;padding:5px 15px;font-size:14px}#sideBar #sideBarMain #sidebar_news #blog-news{padding:15px 15px 0 15px}#sideBar #sideBarMain #sidebar_news #blog-news *{line-height:25px}#sideBar #sideBarMain #sidebar_news #blog-news #profile_block{margin-top:0}#sideBar #sideBarMain #sidebar_categories{background-color:white}#sideBar #sideBarMain #sidebar_categories h3{padding:20px 15px 5px 15px}#sideBar #sideBarMain #sidebar_categories ul{list-style:none;counter-reset:headings;padding:0 15px 15px 15px}#sideBar #sideBarMain #sidebar_categories ul li{line-height:30px;border-bottom:1px dashed #eee}#sideBar #sideBarMain #sidebar_categories ul li:before{counter-increment:headings;content:counter(headings,decimal) ".";font-family:"Bree Serif",serif}#sideBar #sideBarMain #sidebar_categories ul li a{letter-spacing:1px}#sideBar #sideBarMain #sidebar_categories ul li a:hover{color:#dc3545}#footer{font-size:12px;text-align:center;line-height:25px;margin-top:20px;margin-bottom:20px;color:#036}
#footer br{display:none}#main #mainContent .entrylist h1{font-size:18px;font-weight:bolder;text-align:center;margin-top:15px;margin-bottom:30px}#main #mainContent .entrylistItem{position:relative;border:1px dashed lightgrey;padding:15px;margin:0 0 15px 0}#main #mainContent .entrylistItem .entrylistPosttitle a{position:relative;left:-18px;border-left:4px solid #dc3545;padding-left:20px;font-size:18px;font-weight:bolder;color:#333}#main #mainContent .entrylistItem .entrylistPosttitle a:hover{color:#036}#main .entrylistPosttitle span{font-weight:bolder}#main #mainContent .entrylistItem .entrylistPostSummary{padding:10px}#main #mainContent .entrylistItem .entrylistPostSummary .c_b_p_desc{font-size:12px;letter-spacing:2px;opacity:.6;line-height:2;width:900px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#main #mainContent .entrylistItem .entrylistItemPostDesc{font-size:12px;opacity:.6;text-align:right}#main #mainContent .entrylistItem .entrylistItemPostDesc a{font-size:12px;color:#dc3545}#main #post_detail{padding:30px;position:relative}#main #post_detail .postTitle{padding:0 0 50px 0;text-align:center;border-bottom:1px dotted #ccc}#main #post_detail .postTitle a{font-size:20px;color:#333;font-weight:bolder}#main #post_detail .postDesc{position:absolute;width:calc(100% - 60px);top:85px;text-align:center}#main #post_detail .postDesc{font-size:12px;line-height:25px;color:#333;opacity:.7}#main #post_detail .postDesc *{font-size:12px;color:#333;opacity:.7}#main #post_detail .postBody #cnblogs_post_body{margin-top:30px;padding:15px}#main #post_detail .postBody #cnblogs_post_body p,#main #post_detail .postBody #cnblogs_post_body span{letter-spacing:1.5px;line-height:30px;font-size:14px;font-family:SimHei;margin:0;opacity:.9}#main #post_detail .postBody #cnblogs_post_body img{margin:15px 0;max-width:880px}#main #post_detail .postBody #cnblogs_post_body table{width:100%;margin:15px 0}#main #post_detail .postBody #cnblogs_post_body table *{font-size:12px}#main #post_detail .postBody #cnblogs_post_body table th{padding:6px 10px!important;text-align:left;background-color:#1c2b36;color:white;border-color:#1c2b36}#main #post_detail .postBody #cnblogs_post_body table td{padding:6px 10px!important;text-align:left}#main #post_detail .postBody #cnblogs_post_body .cnblogs_code{padding:15px 20px;border:0}#main #post_detail .postBody #cnblogs_post_body .cnblogs_code_toolbar{display:none}#main #post_detail .postBody #cnblogs_post_body blockquote{border:0;background-color:#eee;border-left:5px solid #009688}#main #post_detail .postBody #cnblogs_post_body blockquote *{font-size:12px;color:#333}#main #post_detail .postBody #blog_post_info_block{padding:15px}#main #post_detail .postBody #blog_post_info_block *{font-size:12px;color:#333}#main #post_detail .postBody #blog_post_info_block #author_profile_info{display:none}#main #post_detail .postBody #blog_post_info_block #author_profile_detail{display:none}#main #post_detail .postBody #blog_post_info_block #blog_post_info #green_channel{float:left;border:0}#main #post_detail .postBody #blog_post_info_block #blog_post_info #div_digg{float:right}#main #comment_form{padding:45px!important}#main #comment_form *{font-size:12px}#main #comment_form .comment_textarea{width:100%}#main #blog-comments-placeholder{padding:0 45px!important}#main #blog-comments-placeholder *{font-size:12px;color:#333}#main #blog-comments-placeholder .feedbackItem{padding:15px 0;border-bottom:1px dashed #eee}#main #blog-comments-placeholder .feedbackListSubtitle{line-height:30px}#main #blog-comments-placeholder a{color:#009688}#main #blog-comments-placeholder div{line-height:30px}#main #blog-comments-placeholder .feedbackManage{float:right}#main #blog-comments-placeholder .feedbackListSubtitle .layer{color:#036;font-weight:bolder}#main #blog-comments-placeholder .feedbackListSubtitle .louzhu{color:white;background-color:#1c2b36;padding:0 10px;margin:0 -8px}#main #blog-comments-placeholder br{display:none;padding-left:20px}#main #commentbox_opt #btn_comment_submit{background-color:#009688;color:#fff!important;white-space:nowrap;text-align:center;cursor:pointer;padding-left:10px;padding-right:10px;border:0;width:auto}#main #commentbox_opt a{color:#dc3545}.my-title{font-size:18px!important;font-weight:bolder;font-family:simsun!important;border-left:5px solid #dc3545;padding-left:10px;line-height:20px!important;position:relative;left:-15px}#comment_nav{display:none}#ad_t2{display:none}.c_ad_block{display:none}#under_post_news{display:none}

注意,一定要選擇禁用默認的樣式,否則可能樣式衝突! 

 

3. 配置側邊欄:

代碼如下:

<div style="width: 100%;text-align: center;">
    <img style="border-radius: 50%;border: 1px solid #eee;" src="https://pic.cnblogs.com/face/979767/20180915094029.png" alt="">
</div>
<div>聯繫: Q-1214966109</div>

值得注意的是:

a. 頭像的連接可以在自己的博客中找到,如: 

b. 聯繫方式可以寫自己,如果你還需要其它項目,繼續添加 div 即可!

最終的效果就是我的效果:

 

4. 配置首頁按鈕和 GITHUB 鏈接:

代碼如下:

<script>
    // 菜單新加標籤
    var indexEle = '<li><a target="_blank" class="menu" href="https://www.cnblogs.com/Dy1an">首頁</a></li>';
    var githubEle = '<li><a target="_blank" class="menu" href="https://github.com/KU4NG">GITHUB</a></li><li><a target="_blank" class="menu" href="https://github.com/KU4NG">主題</a></li>';
    document.getElementById('navList').insertAdjacentHTML("beforeEnd", indexEle);
    document.getElementById('navList').insertAdjacentHTML("afterBegin", githubEle);
</script>

需要注意的是:

a. 首頁鏈接記得換成自己的,當然你要是直接用我的我也是不會介意的。

b. GITHUB 你可以選擇換成其它的自己的一些資源。

c. 主題鏈接建議不要去掉,這裏吸波粉,讓我看到到底有多少人是支持這個項目的!

最終效果如下:

 

5. 配置搜索框:

這裏沒有需要修改的,直接粘貼就行,代碼如下:

<script>
    window.onload =  function() {
        var ele = document.getElementById('q');
        console.log(ele);
        ele.setAttribute("placeholder","搜索相關博客...");
        ele.setAttribute("autocomplete","off");
    }
</script>

最終效果:

已知問題:

由於網絡原因可能導致沒有加載或者加載不完全,刷新頁面即可!

 

6. 配置菜單:

想要以我的博客效果显示,則需要配置菜單:

在選項配置中選擇配置!

 

 

特殊用法說明 

 

1. 段落標題:

文中出現段落標題需要瘦動修改 HTML:

增加樣式:

代碼:

class="my-title"

 

2. 表格:

表格同樣需要以 html 格式插入:

<table>
    <thead>
        <th>
            <td>標題</td>
            <td>標題</td>
            <td>標題</td>
        </th>
    </thead>
    <tbody>
        <tr>
            <td>數據行</td>
            <td>數據行</td>
            <td>數據行</td>
        </tr>
        <tr>
            <td>數據行</td>
            <td>數據行</td>
            <td>數據行</td>
        </tr>
    </tbody>
</table>

效果如下:

 

3. 圖片:

最後就是截圖,文中的截圖建議大小不超過 870px,865px最佳,否則圖片會壓縮,影響體驗!

 

 

意見和建議

 

最後就是幾點說明:

1. 如果遇到樣式問題,歡迎反饋,QQ,留言,GITHUB 都可以。

2. 如果覺得可以,可以去 GITHUB star 支持一下,也可以在本文中一下! 

3. 最後吹一波博客園!

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

docker registry 鏡像同步

docker registry 鏡像同步

Intro

之前我們的 docker 鏡像是保存在 Azure 的 Container Registry 里的,最近我們自己搭建了一個 docker registry,我們想把之前保存的 Azure 的 Container Registry 的 docker 鏡像同步到我們自己的 docker registry 里

實現思路

我們的做法比較簡單也比較LOW,但是基本可以滿足要求,

我們的做法是

  1. 首先獲取到源 Registry 里的所有鏡像列表
  2. 然後逐個獲取鏡像的 tags
  3. 然後依次遍歷將對應的鏡像拉到本地,然後 docker tag 一下,命名為新的 registry 鏡像名稱
  4. 然後 push docker 鏡像到新的 registry
  5. 刪除下載到本地的鏡像和推送到新的 registry 的鏡像

後來突然想起來阿里雲好像有一個鏡像同步工具,https://github.com/AliyunContainerService/image-syncer image-syncer 是一個docker鏡像同步工具,可用來進行多對多的鏡像倉庫同步,支持目前絕大多數主流的docker鏡像倉庫服務,看介紹還是很棒的,有需要 registry 之間同步鏡像的可以試試這個工具,看介紹這個工具不會拉取到本地磁盤,從源 registry 獲取鏡像數據之後直接就推送到新的 registry 里了,效率會高很多

Docker-Registry API

docker registry 有一套規範,可以查閱 https://docs.docker.com/registry/spec/api/ 了解更多

獲取所有鏡像

docker registry v2 新增了一個 _catalog 的 api 可以獲取所有的鏡像,v1 可以用 _search 來代替

語法如下:

GET /v2/_catalog

默認最多返回100條記錄,多餘 100 可以通過參數 n 指定返回數量,分頁的話可以指定另外一個參數 last指定完上一頁返回的最後一個鏡像,舉個栗子: http://example.com/v2/_catalog?n=20&last=b

獲取鏡像的 tag

獲取 docker 鏡像的 tag 列表可以使用 GET /v2/<repository-name>/tags/list 來獲取,也可以分頁,類似於上面獲取鏡像列表,可以通過 nlast 來實現分頁加載

操作示例

在本地部署了一個測試用的 docker registry 來做演示,我這裏用 httpie 來做測試

獲取鏡像列表:

調用 _catalog 接口來獲取鏡像列表

http :5000/v2/_catalog

獲取鏡像的 tag 列表

調用 tags/list 接口獲取鏡像的 tag

http :5000/v2/busybox/tags/list
http :5000/v2/redis/tags/list

PowerShell 腳本

一切不是自動化的運維都是耍流氓,很有可能以後會有類似的需求,不如寫個腳本自動化的跑吧

下面的腳本做了一些簡化,因為我們的 azure container registry 上的數量不多,只有五六十個鏡像,而且鏡像只有 latest 的 tag,沒有其他 tag ,所以把上面的步驟做了簡化,並沒有分頁獲取所有的鏡像,也沒有獲取所有的 tag,實際使用的話還請自行修改后使用

# variables
$srcRegUser = "xxx"
$srcRegPwd = "111111"
$srcRegHost = "xxx.azurecr.cn"
$destRegUser = "yyy"
$destRegPwd = "222"
$destRegHost = "registry.xxx.com"


# get repositories from source registry
# httpie
$response = (http -b -a "${srcRegUser}:${srcRegPwd}" "https://${srcRegHost}/v2/_catalog") | ConvertFrom-Json
# curl
#$response = (curl -u "${srcRegUser}:${srcRegPwd}" "https://${srcRegHost}/v2/_catalog") | ConvertFrom-Json
# repository
$repositories = $response.repositories

#
Write-Host $repositories

# login source registry
docker login $srcRegHost -u $srcRegUser -p $srcRegPwd
# login dest registry
docker login $destRegHost -u $destRegUser -p $destRegPwd

# sync
foreach($repo in $repositories)
{
    Write-Host "sync $repo begin"

    $srcTag = "${srcRegHost}/${repo}:latest"
    $destTag = "${destRegHost}/${repo}:latest"

    Write-Host "source image tag: $srcTag"
    Write-Host "dest image tag $destTag"

    Write-Host "docker pull $srcTag begin"

    docker pull $srcTag

    Write-Host "docker pull $srcTag completed"

    Write-Host "docker tag $srcTag $destTag ing"

    docker tag $srcTag $destTag

    Write-Host "docker push $destTag begin"

    docker push $destTag

    Write-Host "docker push $destTag completed"
    
    Write-Host "docker rmi $srcTag $destTag begin"

    docker rmi $srcTag $destTag

    Write-Host "docker rmi $srcTag $destTag end"

    Write-Host "sync $repo completed"
}

Write-Host "Completed..."

More

如果要同步的鏡像比較多,考慮使用阿里雲的鏡像同步工具去同步

Reference

  • https://stackoverflow.com/questions/31251356/how-to-get-a-list-of-images-on-docker-registry-v2
  • https://github.com/AliyunContainerService/image-syncer
  • https://docs.docker.com/registry/spec/api/

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

【K8S】基於Docker+K8S+GitLab/SVN+Jenkins+Harbor搭建持續集成交付環境(環境搭建篇)

環境搭建概述

1.K8S是什麼?

K8S全稱是Kubernetes,是一個全新的基於容器技術的分佈式架構領先方案,基於容器技術,目的是實現資源管理的自動化,以及跨多個數據中心的資源利用率的最大化。

如果我們的系統設計遵循了kubernetes的設計思想,那麼傳統系統架構中那些和業務沒有多大關係的底層代碼或功能模塊,都可以使用K8S來管理,我們不必再費心於負載均衡的選型和部署實施問題,不必再考慮引入或自己開發一個複雜的服務治理框架,不必再頭疼與服務監控和故障處理模塊的開發。總之,使用kubernetes提供的解決方案,會大大減少開發成本,同時可以將精力更加集中於業務本身,而且由於kubernetes提供了強大的自動化機制,所以系統後期的運維難度和運維成本大幅降低。

2.為什麼要用K8S?

Docker 這個新興的容器化技術當前已經被很多公司所採用,其從單機走向集群已成必然,而雲計算的蓬勃發展正在加速這一進程。Kubernetes 作為當前唯一被業界廣泛認可和看好的 Docker 分佈式系統解決方案。可以預見,在未來幾年內,會有大量的新系統選擇它,不管是運行在企業本地服務器上還是被託管到公有雲上。

3.使用K8S有哪些好處?

使用Kubernetes就是在全面部署微服務架構。微服務架構的核心就是將一個巨大的單體應用分解為很多小的互相連接的微服務,一個微服務背後可能有多個實例副本在支撐,副本的數量可能會隨着系統的負荷變化而進行調整,內嵌的負載均衡器在 k8s 平台中有多個實例副本在支撐,副本的數量可能會隨着系統的負荷變化而進行調整,內嵌的負載均衡器 在k8s 平台中發揮了重要的作用。微服務架構使得每個服務都可以由專門的開發團隊來開發,開發者可以自由選擇開發技術,這對於大規模團隊來說很有價值。另外,每個微服務獨立開發、升級、擴展,使得系統具備很高的穩定性和快速迭代進化能力。

4.環境構成

整套環境的搭建包含:Docker環境的搭建、docker-compose環境的搭建、K8S集群的搭建、GitLab代碼倉庫的搭建、SVN倉庫的搭建、Jenkins自動化部署環境的搭建、Harbor私有倉庫的搭建。

本文檔中,整套環境的搭建包括:

  • 安裝Docker環境
  • 安裝docker-compose
  • 安裝K8S集群環境
  • 重啟K8S集群引起的問題
  • K8S安裝ingress-nginx
  • K8S安裝gitlab代碼倉庫
  • 安裝Harbor私有倉庫
  • 安裝Jenkins
  • 物理機安裝SVN(推薦)
  • 物理機安裝Jenkins(推薦)
  • 配置Jenkins運行環境
  • Jenkins發布Docker項目到K8S

服務器規劃

IP 主機名 節點 操作系統
192.168.0.10 test10 K8S Master CentOS 8.0.1905
192.168.0.11 test11 K8S Worker CentOS 8.0.1905
192.168.0.12 test12 K8S Worker CentOS 8.0.1905

安裝環境版本

軟件名稱 軟件版本 說明
Docker 19.03.8 提供容器環境
docker-compose 1.25.5 定義和運行由多個容器組成的應用
K8S 1.18.2 是一個開源的,用於管理雲平台中多個主機上的容器化的應用,Kubernetes的目標是讓部署容器化的應用簡單並且高效(powerful),Kubernetes提供了應用部署,規劃,更新,維護的一種機制。
GitLab 12.1.6 代碼倉庫
Harbor 1.10.2 私有鏡像倉庫
Jenkins 2.222.3 持續集成交付

安裝Docker環境

Docker 是一個開源的應用容器引擎,基於 Go 語言 並遵從 Apache2.0 協議開源。

Docker 可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然後發布到任何流行的 Linux 機器上,也可以實現虛擬化。

本文檔基於Docker 19.03.8 版本搭建Docker環境。

在所有服務器上創建install_docker.sh腳本,腳本內容如下所示。

#使用阿里雲鏡像中心
export REGISTRY_MIRROR=https://registry.cn-hangzhou.aliyuncs.com
#安裝yum工具
dnf install yum*
#安裝docker環境
yum install -y yum-utils device-mapper-persistent-data lvm2
#配置Docker的yum源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#安裝容器插件
dnf install https://mirrors.aliyun.com/docker-ce/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.13-3.1.el7.x86_64.rpm
#指定安裝docker 19.03.8版本
yum install -y docker-ce-19.03.8 docker-ce-cli-19.03.8
#設置Docker開機啟動
systemctl enable docker.service
#啟動Docker
systemctl start docker.service
#查看Docker版本
docker version

在每台服務器上為install_docker.sh腳本賦予可執行權限,並執行腳本,如下所示。

# 賦予install_docker.sh腳本可執行權限
chmod a+x ./install_docker.sh
# 執行install_docker.sh腳本
./install_docker.sh

安裝docker-compose

Compose 是用於定義和運行多容器 Docker 應用程序的工具。通過 Compose,您可以使用 YML 文件來配置應用程序需要的所有服務。然後,使用一個命令,就可以從 YML 文件配置中創建並啟動所有服務。

注意:在每台服務器上安裝docker-compose

1.下載docker-compose文件

#下載並安裝docker-compose
curl -L https://github.com/docker/compose/releases/download/1.25.5/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 

2.為docker-compose文件賦予可執行權限

#賦予docker-compose可執行權限
chmod a+x /usr/local/bin/docker-compose

3.查看docker-compose版本

#查看docker-compose版本
[root@binghe ~]# docker-compose version
docker-compose version 1.25.5, build 8a1c60f6
docker-py version: 4.1.0
CPython version: 3.7.5
OpenSSL version: OpenSSL 1.1.0l  10 Sep 2019

安裝K8S集群環境

Kubernetes是一個開源的,用於管理雲平台中多個主機上的容器化的應用,Kubernetes的目標是讓部署容器化的應用簡單並且高效(powerful),Kubernetes提供了應用部署,規劃,更新,維護的一種機制。

本文檔基於K8S 1.8.12版本來搭建K8S集群

安裝K8S基礎環境

在所有服務器上創建install_k8s.sh腳本文件,腳本文件的內容如下所示。

#################配置阿里雲鏡像加速器開始########################
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://zz3sblpi.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker
######################配置阿里雲鏡像加速器結束#########################
#安裝nfs-utils
yum install -y nfs-utils
#安裝wget軟件下載命令
yum install -y wget

#啟動nfs-server
systemctl start nfs-server
#配置nfs-server開機自啟動
systemctl enable nfs-server

#關閉防火牆
systemctl stop firewalld
#取消防火牆開機自啟動
systemctl disable firewalld

#關閉SeLinux
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

# 關閉 swap
swapoff -a
yes | cp /etc/fstab /etc/fstab_bak
cat /etc/fstab_bak |grep -v swap > /etc/fstab

############################修改 /etc/sysctl.conf開始###########################
# 如果有配置,則修改
sed -i "s#^net.ipv4.ip_forward.*#net.ipv4.ip_forward=1#g"  /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-ip6tables.*#net.bridge.bridge-nf-call-ip6tables=1#g"  /etc/sysctl.conf
sed -i "s#^net.bridge.bridge-nf-call-iptables.*#net.bridge.bridge-nf-call-iptables=1#g"  /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.disable_ipv6.*#net.ipv6.conf.all.disable_ipv6=1#g"  /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.default.disable_ipv6.*#net.ipv6.conf.default.disable_ipv6=1#g"  /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.lo.disable_ipv6.*#net.ipv6.conf.lo.disable_ipv6=1#g"  /etc/sysctl.conf
sed -i "s#^net.ipv6.conf.all.forwarding.*#net.ipv6.conf.all.forwarding=1#g"  /etc/sysctl.conf
# 可能沒有,追加
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-ip6tables = 1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-iptables = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.default.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.lo.disable_ipv6 = 1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding = 1"  >> /etc/sysctl.conf
############################修改 /etc/sysctl.conf結束###########################
# 執行命令使修改后的/etc/sysctl.conf文件生效
sysctl -p

################# 配置K8S的yum源開始#############################
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
       http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
################# 配置K8S的yum源結束#############################

# 卸載舊版本K8S
yum remove -y kubelet kubeadm kubectl

# 安裝kubelet、kubeadm、kubectl,這裏我安裝的是1.18.2版本,你也可以安裝1.17.2版本
yum install -y kubelet-1.18.2 kubeadm-1.18.2 kubectl-1.18.2

# 修改docker Cgroup Driver為systemd
# # 將/usr/lib/systemd/system/docker.service文件中的這一行 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
# # 修改為 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd
# 如果不修改,在添加 worker 節點時可能會碰到如下錯誤
# [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". 
# Please follow the guide at https://kubernetes.io/docs/setup/cri/
sed -i "s#^ExecStart=/usr/bin/dockerd.*#ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --exec-opt native.cgroupdriver=systemd#g" /usr/lib/systemd/system/docker.service

# 設置 docker 鏡像,提高 docker 鏡像下載速度和穩定性
# 如果訪問 https://hub.docker.io 速度非常穩定,也可以跳過這個步驟,一般不需配置
# curl -sSL https://kuboard.cn/install-script/set_mirror.sh | sh -s ${REGISTRY_MIRROR}

# 重新加載配置文件
systemctl daemon-reload
#重啟 docker
systemctl restart docker
# 將kubelet設置為開機啟動並啟動kubelet
systemctl enable kubelet && systemctl start kubelet
# 查看docker版本
docker version

在每台服務器上為install_k8s.sh腳本賦予可執行權限,並執行腳本

# 賦予install_k8s.sh腳本可執行權限
chmod a+x ./install_k8s.sh
# 運行install_k8s.sh腳本
./install_k8s.sh

初始化Master節點

只在test10服務器上執行的操作。

1.初始化Master節點的網絡環境

注意:下面的命令需要在命令行手動執行。

# 只在 master 節點執行
# export 命令只在當前 shell 會話中有效,開啟新的 shell 窗口后,如果要繼續安裝過程,請重新執行此處的 export 命令
export MASTER_IP=192.168.0.10
# 替換 k8s.master 為 您想要的 dnsName
export APISERVER_NAME=k8s.master
# Kubernetes 容器組所在的網段,該網段安裝完成后,由 kubernetes 創建,事先並不存在於物理網絡中
export POD_SUBNET=172.18.0.1/16
echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts

2.初始化Master節點

在test10服務器上創建init_master.sh腳本文件,文件內容如下所示。

#!/bin/bash
# 腳本出錯時終止執行
set -e

if [ ${#POD_SUBNET} -eq 0 ] || [ ${#APISERVER_NAME} -eq 0 ]; then
  echo -e "\033[31;1m請確保您已經設置了環境變量 POD_SUBNET 和 APISERVER_NAME \033[0m"
  echo 當前POD_SUBNET=$POD_SUBNET
  echo 當前APISERVER_NAME=$APISERVER_NAME
  exit 1
fi


# 查看完整配置選項 https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2
rm -f ./kubeadm-config.yaml
cat <<EOF > ./kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.18.2
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
controlPlaneEndpoint: "${APISERVER_NAME}:6443"
networking:
  serviceSubnet: "10.96.0.0/16"
  podSubnet: "${POD_SUBNET}"
  dnsDomain: "cluster.local"
EOF

# kubeadm init
# 初始化kebeadm
kubeadm init --config=kubeadm-config.yaml --upload-certs

# 配置 kubectl
rm -rf /root/.kube/
mkdir /root/.kube/
cp -i /etc/kubernetes/admin.conf /root/.kube/config

# 安裝 calico 網絡插件
# 參考文檔 https://docs.projectcalico.org/v3.13/getting-started/kubernetes/self-managed-onprem/onpremises
echo "安裝calico-3.13.1"
rm -f calico-3.13.1.yaml
wget https://kuboard.cn/install-script/calico/calico-3.13.1.yaml
kubectl apply -f calico-3.13.1.yaml

賦予init_master.sh腳本文件可執行權限並執行腳本。

# 賦予init_master.sh文件可執行權限
chmod a+x ./init_master.sh
# 運行init_master.sh腳本
./init_master.sh

3.查看Master節點的初始化結果

(1)確保所有容器組處於Running狀態

# 執行如下命令,等待 3-10 分鐘,直到所有的容器組處於 Running 狀態
watch kubectl get pod -n kube-system -o wide

具體執行如下所示。

[root@test10 ~]# watch kubectl get pod -n kube-system -o wide
Every 2.0s: kubectl get pod -n kube-system -o wide                                                                                                                          test10: Sun May 10 11:01:32 2020

NAME                                       READY   STATUS    RESTARTS   AGE    IP                NODE        NOMINATED NODE   READINESS GATES          
calico-kube-controllers-5b8b769fcd-5dtlp   1/1     Running   0          118s   172.18.203.66     test10   <none>           <none>          
calico-node-fnv8g                          1/1     Running   0          118s   192.168.0.10   test10   <none>           <none>          
coredns-546565776c-27t7h                   1/1     Running   0          2m1s   172.18.203.67     test10   <none>           <none>          
coredns-546565776c-hjb8z                   1/1     Running   0          2m1s   172.18.203.65     test10   <none>           <none>          
etcd-test10                             1/1     Running   0          2m7s   192.168.0.10   test10   <none>           <none>          
kube-apiserver-test10                   1/1     Running   0          2m7s   192.168.0.10   test10   <none>           <none>          
kube-controller-manager-test10          1/1     Running   0          2m7s   192.168.0.10   test10   <none>           <none>          
kube-proxy-dvgsr                           1/1     Running   0          2m1s   192.168.0.10   test10   <none>           <none>          
kube-scheduler-test10                   1/1     Running   0          2m7s   192.168.0.10   test10   <none>           <none>

(2) 查看 Master 節點初始化結果

# 查看Master節點的初始化結果
kubectl get nodes -o wide

具體執行如下所示。

[root@test10 ~]# kubectl get nodes -o wide
NAME        STATUS   ROLES    AGE     VERSION   INTERNAL-IP       EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION         CONTAINER-RUNTIME
test10   Ready    master   3m28s   v1.18.2   192.168.0.10   <none>        CentOS Linux 8 (Core)   4.18.0-80.el8.x86_64   docker://19.3.8

初始化Worker節點

1.獲取join命令參數

在Master節點(test10服務器)上執行如下命令獲取join命令參數。

kubeadm token create --print-join-command

具體執行如下所示。

[root@test10 ~]# kubeadm token create --print-join-command
W0510 11:04:34.828126   56132 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
kubeadm join k8s.master:6443 --token 8nblts.62xytoqufwsqzko2     --discovery-token-ca-cert-hash sha256:1717cc3e34f6a56b642b5751796530e367aa73f4113d09994ac3455e33047c0d 

其中,有如下一行輸出。

kubeadm join k8s.master:6443 --token 8nblts.62xytoqufwsqzko2     --discovery-token-ca-cert-hash sha256:1717cc3e34f6a56b642b5751796530e367aa73f4113d09994ac3455e33047c0d 

這行代碼就是獲取到的join命令。

注意:join命令中的token的有效時間為 2 個小時,2小時內,可以使用此 token 初始化任意數量的 worker 節點。

2.初始化Worker節點

針對所有的 worker 節點執行,在這裏,就是在test11服務器和test12服務器上執行。

在命令分別手動執行如下命令。

# 只在 worker 節點執行
# 192.168.0.10 為 master 節點的內網 IP
export MASTER_IP=192.168.0.10
# 替換 k8s.master 為初始化 master 節點時所使用的 APISERVER_NAME
export APISERVER_NAME=k8s.master
echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts

# 替換為 master 節點上 kubeadm token create 命令輸出的join
kubeadm join k8s.master:6443 --token 8nblts.62xytoqufwsqzko2     --discovery-token-ca-cert-hash sha256:1717cc3e34f6a56b642b5751796530e367aa73f4113d09994ac3455e33047c0d 

具體執行如下所示。

[root@test11 ~]# export MASTER_IP=192.168.0.10
[root@test11 ~]# export APISERVER_NAME=k8s.master
[root@test11 ~]# echo "${MASTER_IP}    ${APISERVER_NAME}" >> /etc/hosts
[root@test11 ~]# kubeadm join k8s.master:6443 --token 8nblts.62xytoqufwsqzko2     --discovery-token-ca-cert-hash sha256:1717cc3e34f6a56b642b5751796530e367aa73f4113d09994ac3455e33047c0d 
W0510 11:08:27.709263   42795 join.go:346] [preflight] WARNING: JoinControlPane.controlPlane settings will be ignored when control-plane flag is not set.
[preflight] Running pre-flight checks
        [WARNING FileExisting-tc]: tc not found in system path
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.18" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

根據輸出結果可以看出,Worker節點加入了K8S集群。

注意:kubeadm join…就是master 節點上 kubeadm token create 命令輸出的join。

3.查看初始化結果

在Master節點(test10服務器)執行如下命令查看初始化結果。

kubectl get nodes -o wide

具體執行如下所示。

[root@test10 ~]# kubectl get nodes
NAME        STATUS   ROLES    AGE     VERSION
test10   Ready    master   20m     v1.18.2
test11   Ready    <none>   2m46s   v1.18.2
test12   Ready    <none>   2m46s   v1.18.2

注意:kubectl get nodes命令後面加上-o wide參數可以輸出更多的信息。

重啟K8S集群引起的問題

1.Worker節點故障不能啟動

Master 節點的 IP 地址發生變化,導致 worker 節點不能啟動。需要重新安裝K8S集群,並確保所有節點都有固定的內網 IP 地址。

2.Pod崩潰或不能正常訪問

重啟服務器后使用如下命令查看Pod的運行狀態。

#查看所有pod的運行情況
kubectl get pods --all-namespaces

發現很多 Pod 不在 Running 狀態,此時,需要使用如下命令刪除運行不正常的Pod。

kubectl delete pod <pod-name> -n <pod-namespece>

注意:如果Pod 是使用 Deployment、StatefulSet 等控制器創建的,K8S 將創建新的 Pod 作為替代,重新啟動的 Pod 通常能夠正常工作。

其中,pod-name表示運行在K8S中的pod的名稱,pod-namespece表示命名空間。例如,需要刪除pod名稱為pod-test,命名空間為pod-test-namespace的pod,可以使用下面的命令。

kubectl delete pod pod-test -n pod-test-namespace

K8S安裝ingress-nginx

作為反向代理將外部流量導入集群內部,將 Kubernetes 內部的 Service 暴露給外部,在 Ingress 對象中通過域名匹配 Service,這樣就可以直接通過域名訪問到集群內部的服務了。相對於 traefik 來說,nginx-ingress 性能更加優秀。

注意:在Master節點(test10服務器上執行)

1.創建ingress-nginx命名空間

創建ingress-nginx-namespace.yaml文件,主要的作用是創建ingress-nginx命名空間,文件內容如下所示。

apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    name: ingress-nginx

執行如下命令創建ingress-nginx命名空間。

kubectl apply -f ingress-nginx-namespace.yaml

2.安裝ingress controller

創建ingress-nginx-mandatory.yaml文件,主要的作用是安裝ingress-nginx。文件內容如下所示。

apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: default-http-backend
  labels:
    app.kubernetes.io/name: default-http-backend
    app.kubernetes.io/part-of: ingress-nginx
  namespace: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: default-http-backend
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: default-http-backend
        app.kubernetes.io/part-of: ingress-nginx
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: default-http-backend
          # Any image is permissible as long as:
          # 1. It serves a 404 page at /
          # 2. It serves 200 on a /healthz endpoint
          image: registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/defaultbackend-amd64:1.5
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
              scheme: HTTP
            initialDelaySeconds: 30
            timeoutSeconds: 5
          ports:
            - containerPort: 8080
          resources:
            limits:
              cpu: 10m
              memory: 20Mi
            requests:
              cpu: 10m
              memory: 20Mi

---
apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: default-http-backend
    app.kubernetes.io/part-of: ingress-nginx
spec:
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app.kubernetes.io/name: default-http-backend
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: tcp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

kind: ConfigMap
apiVersion: v1
metadata:
  name: udp-services
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: nginx-ingress-clusterrole
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - endpoints
      - nodes
      - pods
      - secrets
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - events
    verbs:
      - create
      - patch
  - apiGroups:
      - "extensions"
    resources:
      - ingresses/status
    verbs:
      - update

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: nginx-ingress-role
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
rules:
  - apiGroups:
      - ""
    resources:
      - configmaps
      - pods
      - secrets
      - namespaces
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - configmaps
    resourceNames:
      # Defaults to "<election-id>-<ingress-class>"
      # Here: "<ingress-controller-leader>-<nginx>"
      # This has to be adapted if you change either parameter
      # when launching the nginx-ingress-controller.
      - "ingress-controller-leader-nginx"
    verbs:
      - get
      - update
  - apiGroups:
      - ""
    resources:
      - configmaps
    verbs:
      - create
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: nginx-ingress-role-nisa-binding
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: nginx-ingress-role
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: nginx-ingress-clusterrole-nisa-binding
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: nginx-ingress-clusterrole
subjects:
  - kind: ServiceAccount
    name: nginx-ingress-serviceaccount
    namespace: ingress-nginx

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        prometheus.io/port: "10254"
        prometheus.io/scrape: "true"
    spec:
      serviceAccountName: nginx-ingress-serviceaccount
      containers:
        - name: nginx-ingress-controller
          image: registry.cn-qingdao.aliyuncs.com/kubernetes_xingej/nginx-ingress-controller:0.20.0
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
            - --udp-services-configmap=$(POD_NAMESPACE)/udp-services
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443
          livenessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1

---

執行如下命令安裝ingress controller。

kubectl apply -f ingress-nginx-mandatory.yaml

3.安裝K8S SVC:ingress-nginx

主要是用來用於暴露pod:nginx-ingress-controller。

創建service-nodeport.yaml文件,文件內容如下所示。

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 30080
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
      nodePort: 30443
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

執行如下命令安裝。

kubectl apply -f service-nodeport.yaml

4.訪問K8S SVC:ingress-nginx

查看ingress-nginx命名空間的部署情況,如下所示。

[root@test10 k8s]# kubectl get pod -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
default-http-backend-796ddcd9b-vfmgn        1/1     Running   1          10h
nginx-ingress-controller-58985cc996-87754   1/1     Running   2          10h

在命令行服務器命令行輸入如下命令查看ingress-nginx的端口映射情況。

kubectl get svc -n ingress-nginx 

具體如下所示。

[root@test10 k8s]# kubectl get svc -n ingress-nginx 
NAME                   TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                      AGE
default-http-backend   ClusterIP   10.96.247.2   <none>        80/TCP                       7m3s
ingress-nginx          NodePort    10.96.40.6    <none>        80:30080/TCP,443:30443/TCP   4m35s

所以,可以通過Master節點(test10服務器)的IP地址和30080端口號來訪問ingress-nginx,如下所示。

[root@test10 k8s]# curl 192.168.0.10:30080       
default backend - 404

也可以在瀏覽器打開http://192.168.0.10:30080 來訪問ingress-nginx,如下所示。

K8S安裝gitlab代碼倉庫

GitLab是由GitLabInc.開發,使用MIT許可證的基於網絡的Git倉庫管理工具,且具有Wiki和issue跟蹤功能。使用Git作為代碼管理工具,並在此基礎上搭建起來的web服務。

注意:在Master節點(test10服務器上執行)

1.創建k8s-ops命名空間

創建k8s-ops-namespace.yaml文件,主要作用是創建k8s-ops命名空間。文件內容如下所示。

apiVersion: v1
kind: Namespace
metadata:
  name: k8s-ops
  labels:
    name: k8s-ops

執行如下命令創建命名空間。

kubectl apply -f k8s-ops-namespace.yaml 

2.安裝gitlab-redis

創建gitlab-redis.yaml文件,文件的內容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: k8s-ops
  labels:
    name: redis
spec:
  selector:
    matchLabels:
      name: redis
  template:
    metadata:
      name: redis
      labels:
        name: redis
    spec:
      containers:
      - name: redis
        image: sameersbn/redis
        imagePullPolicy: IfNotPresent
        ports:
        - name: redis
          containerPort: 6379
        volumeMounts:
        - mountPath: /var/lib/redis
          name: data
        livenessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 30
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - redis-cli
            - ping
          initialDelaySeconds: 10
          timeoutSeconds: 5
      volumes:
      - name: data
        hostPath:
          path: /data1/docker/xinsrv/redis

---
apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: k8s-ops
  labels:
    name: redis
spec:
  ports:
    - name: redis
      port: 6379
      targetPort: redis
  selector:
    name: redis

首先,在命令行執行如下命令創建/data1/docker/xinsrv/redis目錄。

mkdir -p /data1/docker/xinsrv/redis

執行如下命令安裝gitlab-redis。

kubectl apply -f gitlab-redis.yaml 

3.安裝gitlab-postgresql

創建gitlab-postgresql.yaml,文件內容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgresql
  namespace: k8s-ops
  labels:
    name: postgresql
spec:
  selector:
    matchLabels:
      name: postgresql
  template:
    metadata:
      name: postgresql
      labels:
        name: postgresql
    spec:
      containers:
      - name: postgresql
        image: sameersbn/postgresql
        imagePullPolicy: IfNotPresent
        env:
        - name: DB_USER
          value: gitlab
        - name: DB_PASS
          value: passw0rd
        - name: DB_NAME
          value: gitlab_production
        - name: DB_EXTENSION
          value: pg_trgm
        ports:
        - name: postgres
          containerPort: 5432
        volumeMounts:
        - mountPath: /var/lib/postgresql
          name: data
        livenessProbe:
          exec:
            command:
            - pg_isready
            - -h
            - localhost
            - -U
            - postgres
          initialDelaySeconds: 30
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - pg_isready
            - -h
            - localhost
            - -U
            - postgres
          initialDelaySeconds: 5
          timeoutSeconds: 1
      volumes:
      - name: data
        hostPath:
          path: /data1/docker/xinsrv/postgresql
---
apiVersion: v1
kind: Service
metadata:
  name: postgresql
  namespace: k8s-ops
  labels:
    name: postgresql
spec:
  ports:
    - name: postgres
      port: 5432
      targetPort: postgres
  selector:
    name: postgresql

首先,執行如下命令創建/data1/docker/xinsrv/postgresql目錄。

mkdir -p /data1/docker/xinsrv/postgresql

接下來,安裝gitlab-postgresql,如下所示。

kubectl apply -f gitlab-postgresql.yaml

4.安裝gitlab

(1)配置用戶名和密碼

首先,在命令行使用base64編碼為用戶名和密碼進行轉碼,本示例中,使用的用戶名為admin,密碼為admin.1231

轉碼情況如下所示。

[root@test10 k8s]# echo -n 'admin' | base64 
YWRtaW4=
[root@test10 k8s]# echo -n 'admin.1231' | base64 
YWRtaW4uMTIzMQ==

轉碼后的用戶名為:YWRtaW4= 密碼為:YWRtaW4uMTIzMQ==

也可以對base64編碼后的字符串解碼,例如,對密碼字符串解碼,如下所示。

[root@test10 k8s]# echo 'YWRtaW4uMTIzMQ==' | base64 --decode 
admin.1231

接下來,創建secret-gitlab.yaml文件,主要是用戶來配置GitLab的用戶名和密碼,文件內容如下所示。

apiVersion: v1
kind: Secret
metadata:
  namespace: k8s-ops
  name: git-user-pass
type: Opaque
data:
  username: YWRtaW4=
  password: YWRtaW4uMTIzMQ==

執行配置文件的內容,如下所示。

kubectl create -f ./secret-gitlab.yaml

(2)安裝GitLab

創建gitlab.yaml文件,文件的內容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitlab
  namespace: k8s-ops
  labels:
    name: gitlab
spec:
  selector:
    matchLabels:
      name: gitlab
  template:
    metadata:
      name: gitlab
      labels:
        name: gitlab
    spec:
      containers:
      - name: gitlab
        image: sameersbn/gitlab:12.1.6
        imagePullPolicy: IfNotPresent
        env:
        - name: TZ
          value: Asia/Shanghai
        - name: GITLAB_TIMEZONE
          value: Beijing
        - name: GITLAB_SECRETS_DB_KEY_BASE
          value: long-and-random-alpha-numeric-string
        - name: GITLAB_SECRETS_SECRET_KEY_BASE
          value: long-and-random-alpha-numeric-string
        - name: GITLAB_SECRETS_OTP_KEY_BASE
          value: long-and-random-alpha-numeric-string
        - name: GITLAB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: git-user-pass
              key: password
        - name: GITLAB_ROOT_EMAIL
          value: 12345678@qq.com
        - name: GITLAB_HOST
          value: gitlab.binghe.com
        - name: GITLAB_PORT
          value: "80"
        - name: GITLAB_SSH_PORT
          value: "30022"
        - name: GITLAB_NOTIFY_ON_BROKEN_BUILDS
          value: "true"
        - name: GITLAB_NOTIFY_PUSHER
          value: "false"
        - name: GITLAB_BACKUP_SCHEDULE
          value: daily
        - name: GITLAB_BACKUP_TIME
          value: 01:00
        - name: DB_TYPE
          value: postgres
        - name: DB_HOST
          value: postgresql
        - name: DB_PORT
          value: "5432"
        - name: DB_USER
          value: gitlab
        - name: DB_PASS
          value: passw0rd
        - name: DB_NAME
          value: gitlab_production
        - name: REDIS_HOST
          value: redis
        - name: REDIS_PORT
          value: "6379"
        ports:
        - name: http
          containerPort: 80
        - name: ssh
          containerPort: 22
        volumeMounts:
        - mountPath: /home/git/data
          name: data
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 180
          timeoutSeconds: 5
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          timeoutSeconds: 1
      volumes:
      - name: data
        hostPath:
          path: /data1/docker/xinsrv/gitlab
---
apiVersion: v1
kind: Service
metadata:
  name: gitlab
  namespace: k8s-ops
  labels:
    name: gitlab
spec:
  ports:
    - name: http
      port: 80
      nodePort: 30088
    - name: ssh
      port: 22
      targetPort: ssh
      nodePort: 30022
  type: NodePort
  selector:
    name: gitlab

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: gitlab
  namespace: k8s-ops
  annotations:
    kubernetes.io/ingress.class: traefik
spec:
  rules:
  - host: gitlab.binghe.com
    http:
      paths:
      - backend:
          serviceName: gitlab
          servicePort: http

注意:在配置GitLab時,監聽主機時,不能使用IP地址,需要使用主機名或者域名,上述配置中,我使用的是gitlab.binghe.com主機名。

在命令行執行如下命令創建/data1/docker/xinsrv/gitlab目錄。

mkdir -p /data1/docker/xinsrv/gitlab

安裝GitLab,如下所示。

kubectl apply -f gitlab.yaml

5.安裝完成

查看k8s-ops命名空間部署情況,如下所示。

[root@test10 k8s]# kubectl get pod -n k8s-ops
NAME                          READY   STATUS    RESTARTS   AGE
gitlab-7b459db47c-5vk6t       0/1     Running   0          11s
postgresql-79567459d7-x52vx   1/1     Running   0          30m
redis-67f4cdc96c-h5ckz        1/1     Running   1          10h

也可以使用如下命令查看。

[root@test10 k8s]# kubectl get pod --namespace=k8s-ops
NAME                          READY   STATUS    RESTARTS   AGE
gitlab-7b459db47c-5vk6t       0/1     Running   0          36s
postgresql-79567459d7-x52vx   1/1     Running   0          30m
redis-67f4cdc96c-h5ckz        1/1     Running   1          10h

二者效果一樣。

接下來,查看GitLab的端口映射,如下所示。

[root@test10 k8s]# kubectl get svc -n k8s-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                     AGE
gitlab       NodePort    10.96.153.100   <none>        80:30088/TCP,22:30022/TCP   2m42s
postgresql   ClusterIP   10.96.203.119   <none>        5432/TCP                    32m
redis        ClusterIP   10.96.107.150   <none>        6379/TCP                    10h

此時,可以看到,可以通過Master節點(test10)的主機名gitlab.binghe.com和端口30088就能夠訪問GitLab。由於我這裏使用的是虛擬機來搭建相關的環境,在本機訪問虛擬機映射的gitlab.binghe.com時,需要配置本機的hosts文件,在本機的hosts文件中加入如下配置項。

192.168.0.10 gitlab.binghe.com

注意:在Windows操作系統中,hosts文件所在的目錄如下。

C:\Windows\System32\drivers\etc

接下來,就可以在瀏覽器中通過鏈接:http://gitlab.binghe.com:30088 來訪問GitLab了,如下所示。

此時,可以通過用戶名root和密碼admin.1231來登錄GitLab了。

注意:這裏的用戶名是root而不是admin,因為root是GitLab默認的超級用戶。

到此,K8S安裝gitlab完成。

安裝Harbor私有倉庫

Habor是由VMWare公司開源的容器鏡像倉庫。事實上,Habor是在Docker Registry上進行了相應的企業級擴展,從而獲得了更加廣泛的應用,這些新的企業級特性包括:管理用戶界面,基於角色的訪問控制 ,AD/LDAP集成以及審計日誌等,足以滿足基本企業需求。

注意:這裏將Harbor私有倉庫安裝在Master節點(test10服務器)上,實際生產環境中建議安裝在其他服務器。

1.下載Harbor的離線安裝版本

wget https://github.com/goharbor/harbor/releases/download/v1.10.2/harbor-offline-installer-v1.10.2.tgz

2.解壓Harbor的安裝包

tar -zxvf harbor-offline-installer-v1.10.2.tgz

解壓成功后,會在服務器當前目錄生成一個harbor目錄。

3.配置Harbor

注意:這裏,我將Harbor的端口修改成了1180,如果不修改Harbor的端口,默認的端口是80。

(1)修改harbor.yml文件

cd harbor
vim harbor.yml

修改的配置項如下所示。

hostname: 192.168.0.10
http:
  port: 1180
harbor_admin_password: binghe123
###並把https註釋掉,不然在安裝的時候會報錯:ERROR:root:Error: The protocol is https but attribute ssl_cert is not set
#https:
  #port: 443
  #certificate: /your/certificate/path
  #private_key: /your/private/key/path

(2)修改daemon.json文件

修改/etc/docker/daemon.json文件,沒有的話就創建,在/etc/docker/daemon.json文件中添加如下內容。

[root@binghe~]# cat /etc/docker/daemon.json
{
  "registry-mirrors": ["https://zz3sblpi.mirror.aliyuncs.com"],
  "insecure-registries":["192.168.0.10:1180"]
}

也可以在服務器上使用 ip addr 命令查看本機所有的IP地址段,將其配置到/etc/docker/daemon.json文件中。這裏,我配置后的文件內容如下所示。

{
    "registry-mirrors": ["https://zz3sblpi.mirror.aliyuncs.com"],
    "insecure-registries":["192.168.175.0/16","172.17.0.0/16", "172.18.0.0/16", "172.16.29.0/16", "192.168.0.10:1180"]
}

4.安裝並啟動harbor

配置完成后,輸入如下命令即可安裝並啟動Harbor

[root@binghe harbor]# ./install.sh 

5.登錄Harbor並添加賬戶

安裝成功后,在瀏覽器地址欄輸入http://192.168.0.10:1180打開鏈接,輸入用戶名admin和密碼binghe123,登錄系統。

接下來,我們選擇用戶管理,添加一個管理員賬戶,為後續打包Docker鏡像和上傳Docker鏡像做準備。

密碼為Binghe123。點擊確,此時,賬戶binghe還不是管理員,此時選中binghe賬戶,點擊“設置為管理員”。

此時,binghe賬戶就被設置為管理員了。到此,Harbor的安裝就完成了。

6.修改Harbor端口

如果安裝Harbor后,大家需要修改Harbor的端口,可以按照如下步驟修改Harbor的端口,這裏,我以將80端口修改為1180端口為例

(1)修改harbor.yml文件

cd harbor
vim harbor.yml

修改的配置項如下所示。

hostname: 192.168.0.10
http:
  port: 1180
harbor_admin_password: binghe123
###並把https註釋掉,不然在安裝的時候會報錯:ERROR:root:Error: The protocol is https but attribute ssl_cert is not set
#https:
  #port: 443
  #certificate: /your/certificate/path
  #private_key: /your/private/key/path

(2)修改docker-compose.yml文件

vim docker-compose.yml

修改的配置項如下所示。

ports:
      - 1180:80

(3)修改config.yml文件

cd common/config/registry
vim config.yml

修改的配置項如下所示。

realm: http://192.168.0.10:1180/service/token

(4)重啟Docker

systemctl daemon-reload
systemctl restart docker.service

(5)重啟Harbor

[root@binghe harbor]# docker-compose down
Stopping harbor-log ... done
Removing nginx             ... done
Removing harbor-portal     ... done
Removing harbor-jobservice ... done
Removing harbor-core       ... done
Removing redis             ... done
Removing registry          ... done
Removing registryctl       ... done
Removing harbor-db         ... done
Removing harbor-log        ... done
Removing network harbor_harbor
 
[root@binghe harbor]# ./prepare
prepare base dir is set to /mnt/harbor
Clearing the configuration file: /config/log/logrotate.conf
Clearing the configuration file: /config/nginx/nginx.conf
Clearing the configuration file: /config/core/env
Clearing the configuration file: /config/core/app.conf
Clearing the configuration file: /config/registry/root.crt
Clearing the configuration file: /config/registry/config.yml
Clearing the configuration file: /config/registryctl/env
Clearing the configuration file: /config/registryctl/config.yml
Clearing the configuration file: /config/db/env
Clearing the configuration file: /config/jobservice/env
Clearing the configuration file: /config/jobservice/config.yml
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
loaded secret from file: /secret/keys/secretkey
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir
 
[root@binghe harbor]# docker-compose up -d
Creating network "harbor_harbor" with the default driver
Creating harbor-log ... done
Creating harbor-db   ... done
Creating redis       ... done
Creating registry    ... done
Creating registryctl ... done
Creating harbor-core ... done
Creating harbor-jobservice ... done
Creating harbor-portal     ... done
Creating nginx             ... done
 
[root@binghe harbor]# docker ps -a
CONTAINER ID        IMAGE                                               COMMAND                  CREATED             STATUS                             PORTS

安裝Jenkins(一般的做法)

Jenkins是一個開源的、提供友好操作界面的持續集成(CI)工具,起源於Hudson(Hudson是商用的),主要用於持續、自動的構建/測試軟件項目、監控外部任務的運行(這個比較抽象,暫且寫上,不做解釋)。Jenkins用Java語言編寫,可在Tomcat等流行的servlet容器中運行,也可獨立運行。通常與版本管理工具(SCM)、構建工具結合使用。常用的版本控制工具有SVN、GIT,構建工具有Maven、Ant、Gradle。

1.安裝nfs(之前安裝過的話,可以省略此步)

使用 nfs 最大的問題就是寫權限,可以使用 kubernetes 的 securityContext/runAsUser 指定 jenkins 容器中運行 jenkins 的用戶 uid,以此來指定 nfs 目錄的權限,讓 jenkins 容器可寫;也可以不限制,讓所有用戶都可以寫。這裏為了簡單,就讓所有用戶可寫了。

如果之前已經安裝過nfs,則這一步可以省略。找一台主機,安裝 nfs,這裏,我以在Master節點(test10服務器)上安裝nfs為例。

在命令行輸入如下命令安裝並啟動nfs。

yum install nfs-utils -y
systemctl start nfs-server
systemctl enable nfs-server

2.創建nfs共享目錄

在Master節點(test10服務器)上創建 /opt/nfs/jenkins-data目錄作為nfs的共享目錄,如下所示。

mkdir -p /opt/nfs/jenkins-data

接下來,編輯/etc/exports文件,如下所示。

vim /etc/exports

在/etc/exports文件文件中添加如下一行配置。

/opt/nfs/jenkins-data 192.168.175.0/24(rw,all_squash)

這裏的 ip 使用 kubernetes node 節點的 ip 範圍,後面的 all_squash 選項會將所有訪問的用戶都映射成 nfsnobody 用戶,不管你是什麼用戶訪問,最終都會壓縮成 nfsnobody,所以只要將 /opt/nfs/jenkins-data 的屬主改為 nfsnobody,那麼無論什麼用戶來訪問都具有寫權限。

這個選項在很多機器上由於用戶 uid 不規範導致啟動進程的用戶不同,但是同時要對一個共享目錄具有寫權限時很有效。

接下來,為 /opt/nfs/jenkins-data目錄授權,並重新加載nfs,如下所示。

#為/opt/nfs/jenkins-data/目錄授權
chown -R 1000 /opt/nfs/jenkins-data/
#重新加載nfs-server
systemctl reload nfs-server

在K8S集群中任意一個節點上使用如下命令進行驗證:

#查看nfs系統的目錄權限
showmount -e NFS_IP

如果能夠看到 /opt/nfs/jenkins-data 就表示 ok 了。

具體如下所示。

[root@test10 ~]# showmount -e 192.168.0.10
Export list for 192.168.0.10:
/opt/nfs/jenkins-data 192.168.175.0/24

[root@test11 ~]# showmount -e 192.168.0.10
Export list for 192.168.0.10:
/opt/nfs/jenkins-data 192.168.175.0/24

3.創建PV

Jenkins 其實只要加載對應的目錄就可以讀取之前的數據,但是由於 deployment 無法定義存儲卷,因此我們只能使用 StatefulSet。

首先創建 pv,pv 是給 StatefulSet 使用的,每次 StatefulSet 啟動都會通過 volumeClaimTemplates 這個模板去創建 pvc,因此必須得有 pv,才能供 pvc 綁定。

創建jenkins-pv.yaml文件,文件內容如下所示。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins
spec:
  nfs:
    path: /opt/nfs/jenkins-data
    server: 192.168.0.10
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 1Ti

我這裏給了 1T存儲空間,可以根據實際配置。

執行如下命令創建pv。

kubectl apply -f jenkins-pv.yaml 

4.創建serviceAccount

創建service account,因為 jenkins 後面需要能夠動態創建 slave,因此它必須具備一些權限。

創建jenkins-service-account.yaml文件,文件內容如下所示。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins

---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins

上述配置中,創建了一個 RoleBinding 和一個 ServiceAccount,並且將 RoleBinding 的權限綁定到這個用戶上。所以,jenkins 容器必須使用這個 ServiceAccount 運行才行,不然 RoleBinding 的權限它將不具備。

RoleBinding 的權限很容易就看懂了,因為 jenkins 需要創建和刪除 slave,所以才需要上面這些權限。至於 secrets 權限,則是 https 證書。

執行如下命令創建serviceAccount。

kubectl apply -f jenkins-service-account.yaml 

5.安裝Jenkins

創建jenkins-statefulset.yaml文件,文件內容如下所示。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: jenkins
  labels:
    name: jenkins
spec:
  selector:
    matchLabels:
      name: jenkins
  serviceName: jenkins
  replicas: 1
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      name: jenkins
      labels:
        name: jenkins
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccountName: jenkins
      containers:
        - name: jenkins
          image: docker.io/jenkins/jenkins:lts
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
            - containerPort: 32100
          resources:
            limits:
              cpu: 4
              memory: 4Gi
            requests:
              cpu: 4
              memory: 4Gi
          env:
            - name: LIMITS_MEMORY
              valueFrom:
                resourceFieldRef:
                  resource: limits.memory
                  divisor: 1Mi
            - name: JAVA_OPTS
              # value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
              value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
          volumeMounts:
            - name: jenkins-home
              mountPath: /var/jenkins_home
          livenessProbe:
            httpGet:
              path: /login
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
            failureThreshold: 12 # ~2 minutes
          readinessProbe:
            httpGet:
              path: /login
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
            failureThreshold: 12 # ~2 minutes
  # pvc 模板,對應之前的 pv
  volumeClaimTemplates:
    - metadata:
        name: jenkins-home
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Ti

jenkins 部署時需要注意它的副本數,你的副本數有多少就要有多少個 pv,同樣,存儲會有多倍消耗。這裏我只使用了一個副本,因此前面也只創建了一個 pv。

使用如下命令安裝Jenkins。

kubectl apply -f jenkins-statefulset.yaml 

6.創建Service

創建jenkins-service.yaml文件,主要用於後台運行Jenkins,文件內容如下所示。

apiVersion: v1
kind: Service
metadata:
  name: jenkins
spec:
  # type: LoadBalancer
  selector:
    name: jenkins
  # ensure the client ip is propagated to avoid the invalid crumb issue when using LoadBalancer (k8s >=1.7)
  #externalTrafficPolicy: Local
  ports:
    - name: http
      port: 80
      nodePort: 31888
      targetPort: 8080
      protocol: TCP
    - name: jenkins-agent
      port: 32100
      nodePort: 32100
      targetPort: 32100
      protocol: TCP
  type: NodePort

使用如下命令安裝Service。

kubectl apply -f jenkins-service.yaml 

7.安裝 ingress

jenkins 的 web 界面需要從集群外訪問,這裏我們選擇的是使用 ingress。創建jenkins-ingress.yaml文件,文件內容如下所示。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: jenkins
spec:
  rules:
    - http:
        paths:
          - path: /
            backend:
              serviceName: jenkins
              servicePort: 31888
      host: jekins.binghe.com

這裏,需要注意的是host必須配置為域名或者主機名,否則會報錯,如下所示。

The Ingress "jenkins" is invalid: spec.rules[0].host: Invalid value: "192.168.0.10": must be a DNS name, not an IP address

使用如下命令安裝ingress。

kubectl apply -f jenkins-ingress.yaml 

最後,由於我這裏使用的是虛擬機來搭建相關的環境,在本機訪問虛擬機映射的jekins.binghe.com時,需要配置本機的hosts文件,在本機的hosts文件中加入如下配置項。

192.168.0.10 jekins.binghe.com

注意:在Windows操作系統中,hosts文件所在的目錄如下。

C:\Windows\System32\drivers\etc

接下來,就可以在瀏覽器中通過鏈接:http://jekins.binghe.com:31888 來訪問Jekins了。

物理機安裝SVN

Apache Subversion 通常被縮寫成 SVN,是一個開放源代碼的版本控制系統,Subversion 在 2000 年由 CollabNet Inc 開發,現在發展成為 Apache 軟件基金會的一個項目,同樣是一個豐富的開發者和用戶社區的一部分。

SVN相對於的RCS、CVS,採用了分支管理系統,它的設計目標就是取代CVS。互聯網上免費的版本控制服務多基於Subversion。

這裏,以在Master節點(binghe101服務器)上安裝SVN為例。

1.使用yum安裝SVN

在命令行執行如下命令安裝SVN。

yum -y install subversion 

2.創建SVN庫

依次執行如下命令。

#創建/data/svn
mkdir -p /data/svn 
#初始化svn
svnserve -d -r /data/svn
#創建代碼倉庫
svnadmin create /data/svn/test

3.配置SVN

mkdir /data/svn/conf
cp /data/svn/test/conf/* /data/svn/conf/
cd /data/svn/conf/
[root@binghe101 conf]# ll
總用量 20
-rw-r--r-- 1 root root 1080 5月  12 02:17 authz
-rw-r--r-- 1 root root  885 5月  12 02:17 hooks-env.tmpl
-rw-r--r-- 1 root root  309 5月  12 02:17 passwd
-rw-r--r-- 1 root root 4375 5月  12 02:17 svnserve.conf
  • 配置authz文件,
vim authz

配置后的內容如下所示。

[aliases]
# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average

[groups]
# harry_and_sally = harry,sally
# harry_sally_and_joe = harry,sally,&joe
SuperAdmin = admin
binghe = admin,binghe

# [/foo/bar]
# harry = rw
# &joe = r
# * =

# [repository:/baz/fuz]
# @harry_and_sally = rw
# * = r

[test:/]
@SuperAdmin=rw
@binghe=rw
  • 配置passwd文件
vim passwd

配置后的內容如下所示。

[users]
# harry = harryssecret
# sally = sallyssecret
admin = admin123
binghe = binghe123
  • 配置 svnserve.conf
vim svnserve.conf

配置后的文件如下所示。

### This file controls the configuration of the svnserve daemon, if you
### use it to allow access to this repository.  (If you only allow
### access through http: and/or file: URLs, then this file is
### irrelevant.)

### Visit http://subversion.apache.org/ for more information.

[general]
### The anon-access and auth-access options control access to the
### repository for unauthenticated (a.k.a. anonymous) users and
### authenticated users, respectively.
### Valid values are "write", "read", and "none".
### Setting the value to "none" prohibits both reading and writing;
### "read" allows read-only access, and "write" allows complete 
### read/write access to the repository.
### The sample settings below are the defaults and specify that anonymous
### users have read-only access to the repository, while authenticated
### users have read and write access to the repository.
anon-access = none
auth-access = write
### The password-db option controls the location of the password
### database file.  Unless you specify a path starting with a /,
### the file's location is relative to the directory containing
### this configuration file.
### If SASL is enabled (see below), this file will NOT be used.
### Uncomment the line below to use the default password file.
password-db = /data/svn/conf/passwd
### The authz-db option controls the location of the authorization
### rules for path-based access control.  Unless you specify a path
### starting with a /, the file's location is relative to the
### directory containing this file.  The specified path may be a
### repository relative URL (^/) or an absolute file:// URL to a text
### file in a Subversion repository.  If you don't specify an authz-db,
### no path-based access control is done.
### Uncomment the line below to use the default authorization file.
authz-db = /data/svn/conf/authz
### The groups-db option controls the location of the file with the
### group definitions and allows maintaining groups separately from the
### authorization rules.  The groups-db file is of the same format as the
### authz-db file and should contain a single [groups] section with the
### group definitions.  If the option is enabled, the authz-db file cannot
### contain a [groups] section.  Unless you specify a path starting with
### a /, the file's location is relative to the directory containing this
### file.  The specified path may be a repository relative URL (^/) or an
### absolute file:// URL to a text file in a Subversion repository.
### This option is not being used by default.
# groups-db = groups
### This option specifies the authentication realm of the repository.
### If two repositories have the same authentication realm, they should
### have the same password database, and vice versa.  The default realm
### is repository's uuid.
realm = svn
### The force-username-case option causes svnserve to case-normalize
### usernames before comparing them against the authorization rules in the
### authz-db file configured above.  Valid values are "upper" (to upper-
### case the usernames), "lower" (to lowercase the usernames), and
### "none" (to compare usernames as-is without case conversion, which
### is the default behavior).
# force-username-case = none
### The hooks-env options specifies a path to the hook script environment 
### configuration file. This option overrides the per-repository default
### and can be used to configure the hook script environment for multiple 
### repositories in a single file, if an absolute path is specified.
### Unless you specify an absolute path, the file's location is relative
### to the directory containing this file.
# hooks-env = hooks-env

[sasl]
### This option specifies whether you want to use the Cyrus SASL
### library for authentication. Default is false.
### Enabling this option requires svnserve to have been built with Cyrus
### SASL support; to check, run 'svnserve --version' and look for a line
### reading 'Cyrus SASL authentication is available.'
# use-sasl = true
### These options specify the desired strength of the security layer
### that you want SASL to provide. 0 means no encryption, 1 means
### integrity-checking only, values larger than 1 are correlated
### to the effective key length for encryption (e.g. 128 means 128-bit
### encryption). The values below are the defaults.
# min-encryption = 0
# max-encryption = 256

接下來,將/data/svn/conf目錄下的svnserve.conf文件複製到/data/svn/test/conf/目錄下。如下所示。

[root@binghe101 conf]# cp /data/svn/conf/svnserve.conf /data/svn/test/conf/
cp:是否覆蓋'/data/svn/test/conf/svnserve.conf'? y

4.啟動SVN服務

(1)創建svnserve.service服務

創建svnserve.service文件

vim /usr/lib/systemd/system/svnserve.service

文件的內容如下所示。

[Unit]
Description=Subversion protocol daemon
After=syslog.target network.target
Documentation=man:svnserve(8)

[Service]
Type=forking
EnvironmentFile=/etc/sysconfig/svnserve
#ExecStart=/usr/bin/svnserve --daemon --pid-file=/run/svnserve/svnserve.pid $OPTIONS
ExecStart=/usr/bin/svnserve --daemon $OPTIONS
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

接下來執行如下命令使配置生效。

systemctl daemon-reload

命令執行成功后,修改 /etc/sysconfig/svnserve 文件。

vim /etc/sysconfig/svnserve 

修改后的文件內容如下所示。

# OPTIONS is used to pass command-line arguments to svnserve.
# 
# Specify the repository location in -r parameter:
OPTIONS="-r /data/svn"

(2)啟動SVN

首先查看SVN狀態,如下所示。

[root@test10 conf]# systemctl status svnserve.service
● svnserve.service - Subversion protocol daemon
   Loaded: loaded (/usr/lib/systemd/system/svnserve.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:svnserve(8)

可以看到,此時SVN並沒有啟動,接下來,需要啟動SVN。

systemctl start svnserve.service

設置SVN服務開機自啟動。

systemctl enable svnserve.service

接下來,就可以下載安裝TortoiseSVN,輸入鏈接svn://192.168.0.10/test 並輸入用戶名binghe,密碼binghe123來連接SVN了。

Docker安裝SVN

拉取SVN鏡像

docker pull docker.io/elleflorio/svn-server

啟動SVN容器

docker run -v /usr/local/svn:/home/svn -v /usr/local/svn/passwd:/etc/subversion/passwd -v /usr/local/apache2:/run/apache2 --name svn_server -p 3380:80 -p 3690:3960 -e SVN_REPONAME=repos -d docker.io/elleflorio/svn-server

進入SVN容器內部

docker exec -it svn_server bash

進入容器后,可以參照物理機安裝SVN的方式配置SVN倉庫。

物理機安裝Jenkins

注意:安裝Jenkins之前需要安裝JDK和Maven,我這裏同樣將Jenkins安裝在Master節點(binghe101服務器)。

1.啟用Jenkins庫

運行以下命令以下載repo文件並導入GPG密鑰:

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat-stable/jenkins.repo
rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key

2.安裝Jenkins

執行如下命令安裝Jenkis。

yum install jenkins

接下來,修改Jenkins默認端口,如下所示。

vim /etc/sysconfig/jenkins

修改后的兩項配置如下所示。

JENKINS_JAVA_CMD="/usr/local/jdk1.8.0_212/bin/java"
JENKINS_PORT="18080"

此時,已經將Jenkins的端口由8080修改為18080

3.啟動Jenkins

在命令行輸入如下命令啟動Jenkins。

systemctl start jenkins

配置Jenkins開機自啟動。

systemctl enable jenkins

查看Jenkins的運行狀態。

[root@test10 ~]# systemctl status jenkins
● jenkins.service - LSB: Jenkins Automation Server
   Loaded: loaded (/etc/rc.d/init.d/jenkins; generated)
   Active: active (running) since Tue 2020-05-12 04:33:40 EDT; 28s ago
     Docs: man:systemd-sysv-generator(8)
    Tasks: 71 (limit: 26213)
   Memory: 550.8M

說明,Jenkins啟動成功。

配置Jenkins運行環境

1.登錄Jenkins

首次安裝后,需要配置Jenkins的運行環境。首先,在瀏覽器地址欄訪問鏈接http://192.168.0.10:18080,打開Jenkins界面。

根據提示使用如下命令到服務器上找密碼值,如下所示。

[root@binghe101 ~]# cat /var/lib/jenkins/secrets/initialAdminPassword
71af861c2ab948a1b6efc9f7dde90776

將密碼71af861c2ab948a1b6efc9f7dde90776複製到文本框,點擊繼續。會跳轉到自定義Jenkins頁面,如下所示。

這裏,可以直接選擇“安裝推薦的插件”。之後會跳轉到一個安裝插件的頁面,如下所示。

此步驟可能有下載失敗的情況,可直接忽略。

2.安裝插件

需要安裝的插件

  • Kubernetes Cli Plugin:該插件可直接在Jenkins中使用kubernetes命令行進行操作。
  • Kubernetes plugin: 使用kubernetes則需要安裝該插件
  • Kubernetes Continuous Deploy Plugin:kubernetes部署插件,可根據需要使用

還有更多的插件可供選擇,可點擊 系統管理->管理插件進行管理和添加,安裝相應的Docker插件、SSH插件、Maven插件。其他的插件可以根據需要進行安裝。如下圖所示。

3.配置Jenkins

(1)配置JDK和Maven

在Global Tool Configuration中配置JDK和Maven,如下所示,打開Global Tool Configuration界面。

接下來就開始配置JDK和Maven了。

由於我在服務器上將Maven安裝在/usr/local/maven-3.6.3目錄下,所以,需要在“Maven 配置”中進行配置,如下圖所示。

接下來,配置JDK,如下所示。

注意:不要勾選“Install automatically”

接下來,配置Maven,如下所示。

注意:不要勾選“Install automatically”

(2)配置SSH

進入Jenkins的Configure System界面配置SSH,如下所示。

找到 SSH remote hosts 進行配置。

配置完成后,點擊Check connection按鈕,會显示 Successfull connection。如下所示。

至此,Jenkins的基本配置就完成了。

Jenkins發布Docker項目到K8s集群

1.調整SpringBoot項目的配置

實現,SpringBoot項目中啟動類所在的模塊的pom.xml需要引入打包成Docker鏡像的配置,如下所示。

  <properties>
  	 	<docker.repostory>192.168.0.10:1180</docker.repostory>
        <docker.registry.name>test</docker.registry.name>
        <docker.image.tag>1.0.0</docker.image.tag>
        <docker.maven.plugin.version>1.4.10</docker.maven.plugin.version>
  </properties>

<build>
  		<finalName>test-starter</finalName>
		<plugins>
            <plugin>
			    <groupId>org.springframework.boot</groupId>
			    <artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			
			<!-- docker的maven插件,官網:https://github.com/spotify/docker‐maven‐plugin -->
			<!-- Dockerfile maven plugin -->
			<plugin>
			    <groupId>com.spotify</groupId>
			    <artifactId>dockerfile-maven-plugin</artifactId>
			    <version>${docker.maven.plugin.version}</version>
			    <executions>
			        <execution>
			        <id>default</id>
			        <goals>
			            <!--如果package時不想用docker打包,就註釋掉這個goal-->
			            <goal>build</goal>
			            <goal>push</goal>
			        </goals>
			        </execution>
			    </executions>
			    <configuration>
			    	<contextDirectory>${project.basedir}</contextDirectory>
			        <!-- harbor 倉庫用戶名及密碼-->
			        <useMavenSettingsForAuth>useMavenSettingsForAuth>true</useMavenSettingsForAuth>
			        <repository>${docker.repostory}/${docker.registry.name}/${project.artifactId}</repository>
			        <tag>${docker.image.tag}</tag>
			        <buildArgs>
			            <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
			        </buildArgs>
			    </configuration>
			</plugin>

        </plugins>
        
		<resources>
			<!-- 指定 src/main/resources下所有文件及文件夾為資源文件 -->
			<resource>
				<directory>src/main/resources</directory>
				<targetPath>${project.build.directory}/classes</targetPath>
				<includes>
					<include>**/*</include>
				</includes>
				<filtering>true</filtering>
			</resource>
		</resources>
	</build>

接下來,在SpringBoot啟動類所在模塊的根目錄創建Dockerfile,內容示例如下所示。

#添加依賴環境,前提是將Java8的Docker鏡像從官方鏡像倉庫pull下來,然後上傳到自己的Harbor私有倉庫中
FROM 192.168.0.10:1180/library/java:8
#指定鏡像製作作者
MAINTAINER binghe
#運行目錄
VOLUME /tmp
#將本地的文件拷貝到容器
ADD target/*jar app.jar
#啟動容器后自動執行的命令
ENTRYPOINT [ "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar" ]

根據實際情況,自行修改。

注意:FROM 192.168.0.10:1180/library/java:8的前提是執行如下命令。

docker pull java:8
docker tag java:8 192.168.0.10:1180/library/java:8
docker login 192.168.0.10:1180
docker push 192.168.0.10:1180/library/java:8

在SpringBoot啟動類所在模塊的根目錄創建yaml文件,錄入叫做test.yaml文件,內容如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-starter
  labels:
    app: test-starter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-starter
  template:
    metadata:
      labels:
        app: test-starter
    spec:
      containers:
      - name: test-starter
        image: 192.168.0.10:1180/test/test-starter:1.0.0
        ports:
        - containerPort: 8088
      nodeSelector:
        clustertype: node12

---
apiVersion: v1
kind: Service
metadata:
  name: test-starter
  labels:
    app: test-starter
spec:
  ports:
    - name: http
      port: 8088
      nodePort: 30001
  type: NodePort
  selector:
    app: test-starter

2.Jenkins配置發布項目

將項目上傳到SVN代碼庫,例如地址為svn://192.168.0.10/test

接下來,在Jenkins中配置自動發布。步驟如下所示。

點擊新建Item。

在描述文本框中輸入描述信息,如下所示。

接下來,配置SVN信息。

注意:配置GitLab的步驟與SVN相同,不再贅述。

定位到Jenkins的“構建模塊”,使用Execute Shell來構建發布項目到K8S集群。

執行的命令依次如下所示。

#刪除本地原有的鏡像,不會影響Harbor倉庫中的鏡像
docker rmi 192.168.0.10:1180/test/test-starter:1.0.0
#使用Maven編譯、構建Docker鏡像,執行完成后本地Docker容器中會重新構建鏡像文件
/usr/local/maven-3.6.3/bin/mvn -f ./pom.xml clean install -Dmaven.test.skip=true
#登錄 Harbor倉庫
docker login 192.168.0.10:1180 -u binghe -p Binghe123
#上傳鏡像到Harbor倉庫
docker push 192.168.0.10:1180/test/test-starter:1.0.0
#停止並刪除K8S集群中運行的
/usr/bin/kubectl delete -f test.yaml
#將Docker鏡像重新發布到K8S集群
/usr/bin/kubectl apply -f test.yaml

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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