分享我在前後端分離項目中Gitlab-CI的經驗

長話短說,今天分享我為前後端分離項目搭建Gitlab CI/CD流程的一些額外經驗。

Before

Gitlab-ci是Gitlab提供的CI/CD特性,結合Gitlab簡單友好的配置界面,能愉悅的在Gitlab界面查看管道執行流程,並自然流暢的推動敏捷開發流程。
Gitlab-CI/CD的核心是搭建Gitlab Runner、編寫.gitlab-ci.yaml文件。
詳細示例請參考:Gitlab CI/CD+ASP.NETCore.

本次前後端兩個項目使用同一個Gitlab Runner(shell模式),前端項目的gitlab-ci.yaml構建Job如圖:

Round 1

單個Gitlab Runner可為多個項目提供構建服務,

gitlab-Runner register命令只能接受一個註冊token,當時為支持多個項目,花了不少冤枉心思倒騰Gitlab Runner.

你可以為註冊的項目解鎖Runner,這樣Girlab Runner就可以為其他項目提供構建:

Round 2

使用Runner緩存加快前端構建過程
大家都知道npm_module被前端開發者詬病為毒瘤, 而Gitlab runner執行每次構建job之前都會清場,pull/fetch指定的代碼再執行job, 這就導致每次build job會耗時很久(要拉取毒瘤)。

#!/bin/bash

cd   packages/event-analysis
yarn config set registry http://registry.npm.gridsum.com &&  yarn --prefer-offline --frozen-lockfile
npm run build

以上是build任務的腳本frontend.sh,總耗時3m33s,其中yarn命令拉取npm_modules耗時172.52s

gitlab runner支持緩存
在.gitlab-ci.yaml 文件中定義cache指令:
cache被用來在job之間緩存文件,更強大的是可以定義文件依賴緩存:

build:
  stage: build
  cache:
    key:
      files:
        - packages/event-analysis/package.json
    paths:
      - node_modules
  script: 
    - ./frontend.sh
  tags:
    - my-tag

緩存key是yarn命令要用到的package.json,緩存內容是npm_modules;
只要這個package.json文件未變更,後續任務就會使用緩存的npm_modules,而不用重建npm_modules依賴。

使用runner緩存優化后build任務總耗時1m18s,其中yarn命令耗時22.83s:

以上針對Gitlab-CI的使用經驗點到為止,足夠應對我當前項目,更多請關注:

Reference

  1. https://docs.gitlab.com/ee/ci/runners/#prevent-a-specific-runner-from-being-enabled-for-other-projects
  2. https://docs.gitlab.com/ee/ci/caching/

Devops的圈子很大,上面的Gitlab-ci也只是點到為止,應付我當前的前後端分離項目.. 歡迎大家來捶我。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

博客園自用主題美化 – 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 來獲取,也可以分頁,類似於上面獲取鏡像列表,可以通過 n 和 last 來實現分頁加載

操作示例

在本地部署了一個測試用的 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/

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

.NET Core加解密實戰系列之——RSA非對稱加密算法

目錄

  • 簡介
  • 功能依賴
  • 生成RSA秘鑰
    • PKCS1格式
    • PKCS8格式
  • 私鑰操作
    • PKCS1與PKCS8格式互轉
    • PKCS1與PKCS8私鑰中提取公鑰
  • PEM操作
    • PEM格式密鑰讀取
    • PEM格式密鑰寫入
  • RSA加解密
    • 獲取非對稱秘鑰參數(AsymmetricKeyParameter)
    • RSA加解與解密
    • RSA密文算法
  • 編碼算法
    • BouncyCastle提供的Base64編碼算法
    • BouncyCastle提供的Hex十六進制編碼算法
  • RSA加解密示例
  • 下期預告

簡介

加解密現狀,編寫此項目的背景:

  • 需要考慮系統環境兼容性問題(Linux、Windows)
  • 語言互通問題(如C#、Java)
  • 網上資料版本不一、不全面
  • .NET官方庫密碼算法提供不全面,很難針對其他語言(Java)進行適配

本系列文章主要介紹如何結合BouncyCastle在 .NET Core 中使用非對稱加密算法、編碼算法、哈希算法、對稱加密算法、國密算法等一系列算法,內容篇幅代碼居多(加解密算法相關的原理知識網上有很多,因此不過多介紹)。如有錯誤之處,還請大家批評指正。

本系列代碼項目地址:https://github.com/fuluteam/ICH.BouncyCastle.git

功能依賴

BouncyCastle(https://www.bouncycastle.org/csharp) 是一個開放源碼的輕量級密碼術包;它支持大量的密碼術算法,它提供了很多.NET Core標準庫沒有的算法。

支持.NET 4,.NET Standard 1.0-2.0,WP,Silverlight,MonoAndroid,Xamarin.iOS,.NET Core

功能 依賴
Portable.BouncyCastle Portable.BouncyCastle • 1.8.5

生成RSA秘鑰

PKCS1格式

/// <summary>
/// PKCS1(非Java適用)
/// </summary>
/// <param name="keySize">密鑰長度”一般只是指模值的位長度。目前主流可選值:1024、2048、3072、4096...</param>
/// <param name="format">PEM格式</param>
/// <returns></returns>
public RSAKeyParameter Pkcs1(int keySize, bool format=false)
{
    var keyGenerator = GeneratorUtilities.GetKeyPairGenerator("RSA");
    keyGenerator.Init(new KeyGenerationParameters(new SecureRandom(), keySize));

    var keyPair = keyGenerator.GenerateKeyPair();

    var subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
    var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
    
    if (!format)
    {
        return new RSAKeyParameter
        {
            PrivateKey = Base64.ToBase64String(privateKeyInfo.ParsePrivateKey().GetEncoded()),
            PublicKey = Base64.ToBase64String(subjectPublicKeyInfo.GetEncoded())
        };
    }

    var rsaKey = new RSAKeyParameter();
    using (var sw = new StringWriter())
    {
        var pWrt = new PemWriter(sw);
        pWrt.WriteObject(keyPair.Private);
        pWrt.Writer.Close();
        rsaKey.PrivateKey = sw.ToString();
    }

    using (var sw = new StringWriter())
    {
        var pWrt = new PemWriter(sw);
        pWrt.WriteObject(keyPair.Public);
        pWrt.Writer.Close();
        rsaKey.PublicKey = sw.ToString();
    }

    return rsaKey;
}

PKCS8格式

/// <summary>
/// PKCS8(JAVA適用)
/// </summary>
/// <param name="keySize">密鑰長度”一般只是指模值的位長度。目前主流可選值:1024、2048、3072、4096...</param>
/// <param name="format">PEM格式</param>
/// <returns></returns>
public RSAKeyParameter Pkcs8(int keySize, bool format=false)
{
    var keyGenerator = GeneratorUtilities.GetKeyPairGenerator("RSA");
    keyGenerator.Init(new KeyGenerationParameters(new SecureRandom(), keySize));
    var keyPair = keyGenerator.GenerateKeyPair();

    var subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
    var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);

    if (!format)
    {
        return new RSAKeyParameter
        {
            PrivateKey = Base64.ToBase64String(privateKeyInfo.GetEncoded()),
            PublicKey = Base64.ToBase64String(subjectPublicKeyInfo.GetEncoded())
        };
    }

    var rsaKey = new RSAKeyParameter();
    using (var sw = new StringWriter())
    {
        var pWrt = new PemWriter(sw);
        var pkcs8 = new Pkcs8Generator(keyPair.Private);
        pWrt.WriteObject(pkcs8);
        pWrt.Writer.Close();
        rsaKey.PrivateKey = sw.ToString();
    }

    using (var sw = new StringWriter())
    {
        var pWrt = new PemWriter(sw);
        pWrt.WriteObject(keyPair.Public);
        pWrt.Writer.Close();
        rsaKey.PublicKey = sw.ToString();
    }

    return rsaKey;
}

私鑰操作

PKCS1與PKCS8格式互轉

僅私鑰有PKCS1和PKCS8之分,公鑰無格式區別。

 /// <summary>
 /// Pkcs1>>Pkcs8
 /// </summary>
 /// <param name="privateKey">Pkcs1私鑰</param>
 /// <param name="format">是否轉PEM格式</param>
 /// <returns></returns>
 public static string PrivateKeyPkcs1ToPkcs8(string privateKey, bool format = false)
 {
     var akp = RSAUtilities.GetAsymmetricKeyParameterFormPrivateKey(privateKey);
     if (format)
     {
         var sw = new StringWriter();
         var pWrt = new PemWriter(sw);
         var pkcs8 = new Pkcs8Generator(akp);
         pWrt.WriteObject(pkcs8);
         pWrt.Writer.Close();
         return sw.ToString();
     }
     else
     {
         var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(akp);
         return Base64.ToBase64String(privateKeyInfo.GetEncoded());
     }
 }
/// <summary>
/// Pkcs8>>Pkcs1
/// </summary>
/// <param name="privateKey">Pkcs8私鑰</param>
/// <param name="format">是否轉PEM格式</param>
/// <returns></returns>
public static string PrivateKeyPkcs8ToPkcs1(string privateKey, bool format = false)
{
    var akp = RSAUtilities.GetAsymmetricKeyParameterFormAsn1PrivateKey(privateKey);
    if (format)
    {
        var sw = new StringWriter();
        var pWrt = new PemWriter(sw);
        pWrt.WriteObject(akp);
        pWrt.Writer.Close();
        return sw.ToString();
    }
    else
    {
        var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(akp);
        return Base64.ToBase64String(privateKeyInfo.ParsePrivateKey().GetEncoded());
    }
}

PKCS1與PKCS8私鑰中提取公鑰

/// <summary>
/// 從Pkcs1私鑰中提取公鑰
/// </summary>
/// <param name="privateKey">Pkcs1私鑰</param>
/// <returns></returns>
public static string GetPublicKeyFromPrivateKeyPkcs1(string privateKey)
{
    var instance = RsaPrivateKeyStructure.GetInstance(Base64.Decode(privateKey));

    var publicParameter = (AsymmetricKeyParameter)new RsaKeyParameters(false, instance.Modulus,instance.PublicExponent);

    var privateParameter = (AsymmetricKeyParameter)new RsaPrivateCrtKeyParameters(instance.Modulus,instance.PublicExponent, instance.PrivateExponent, instance.Prime1, instance.Prime2, instance.Exponent1,instance.Exponent2, instance.Coefficient);

    var keyPair = new AsymmetricCipherKeyPair(publicParameter, privateParameter);
    var subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);

    return Base64.ToBase64String(subjectPublicKeyInfo.GetEncoded());
}
/// <summary>
/// 從Pkcs8私鑰中提取公鑰
/// </summary>
/// <param name="privateKey">Pkcs8私鑰</param>
/// <returns></returns>
public static string GetPublicKeyFromPrivateKeyPkcs8(string privateKey)
{
    var privateKeyInfo = PrivateKeyInfo.GetInstance(Asn1Object.FromByteArray(Base64.Decode(privateKey)));
    privateKey = Base64.ToBase64String(privateKeyInfo.ParsePrivateKey().GetEncoded());

    var instance = RsaPrivateKeyStructure.GetInstance(Base64.Decode(privateKey));

    var publicParameter = (AsymmetricKeyParameter)new RsaKeyParameters(false, instance.Modulus,instance.PublicExponent);

    var privateParameter = (AsymmetricKeyParameter)new RsaPrivateCrtKeyParameters(instance.Modulus,instance.PublicExponent, instance.PrivateExponent, instance.Prime1, instance.Prime2, instance.Exponent1,instance.Exponent2, instance.Coefficient);

    var keyPair = new AsymmetricCipherKeyPair(publicParameter, privateParameter);
    var subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);

    return Base64.ToBase64String(subjectPublicKeyInfo.GetEncoded());
}

PEM操作

PEM格式密鑰讀取

public static string ReadPkcs1PrivateKey(string text)
{
    if (!text.StartsWith("-----BEGIN RSA PRIVATE KEY-----"))
    {
        return text;
    }

    using (var reader = new StringReader(text))
    {
        var pr = new PemReader(reader);
        var keyPair = pr.ReadObject() as AsymmetricCipherKeyPair;
        pr.Reader.Close();

        var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair?.Private);
        return Base64.ToBase64String(privateKeyInfo.ParsePrivateKey().GetEncoded());
    }
}

public static string ReadPkcs8PrivateKey(string text)
{
    if (!text.StartsWith("-----BEGIN PRIVATE KEY-----"))
    {
        return text;
    }

    using (var reader = new StringReader(text))
    {
        var pr = new PemReader(reader);
        var akp = pr.ReadObject() as AsymmetricKeyParameter; ;
        pr.Reader.Close();
        return Base64.ToBase64String(PrivateKeyInfoFactory.CreatePrivateKeyInfo(akp).GetEncoded());
    }
}

 public static string ReadPublicKey(string text)
 {
    if (!text.StartsWith("-----BEGIN PUBLIC KEY-----"))
    {
        return text;
    }
    using (var reader = new StringReader(text))
    {
        var pr = new PemReader(reader);
        var keyPair = pr.ReadObject() as AsymmetricCipherKeyPair;
        pr.Reader.Close();

        var subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair?.Public);
        returnBase64.ToBase64String(subjectPublicKeyIno.GetEncoded());
    }
 }

PEM格式密鑰寫入

public static string WritePkcs1PrivateKey(string privateKey)
{
    if (privateKey.StartsWith("-----BEGIN RSA PRIVATE KEY-----"))
    {
        return privateKey;
    }

    var akp = RSAUtilities.GetAsymmetricKeyParameterFormPrivateKey(privateKey);
    using (var sw = new StringWriter())
    {
        var pWrt = new PemWriter(sw);
        pWrt.WriteObject(akp);
        pWrt.Writer.Close();
        return sw.ToString();
    }
}

public static string WritePkcs8PrivateKey(string privateKey)
{
    if (privateKey.StartsWith("-----BEGIN PRIVATE KEY-----"))
    {
        return privateKey;
    }

    var akp = RSAUtilities.GetAsymmetricKeyParameterFormAsn1PrivateKey(privateKey);

    using (var sw = new StringWriter())
    {
        var pWrt = new PemWriter(sw);
        var pkcs8 = new Pkcs8Generator(akp);
        pWrt.WriteObject(pkcs8);
        pWrt.Writer.Close();
        return sw.ToString();
    }
}

public static string WritePublicKey(string publicKey)
{
    if (publicKey.StartsWith("-----BEGIN PUBLIC KEY-----"))
    {
        return publicKey;
    }
    var akp = RSAUtilities.GetAsymmetricKeyParameterFormPublicKey(publicKey);
    using (var sw = new StringWriter())
    {
        var pWrt = new PemWriter(sw);
        pWrt.WriteObject(akp);
        pWrt.Writer.Close();
        return sw.ToString();
    }
}

RSA加解密

獲取非對稱秘鑰參數(AsymmetricKeyParameter)

/// <summary>
/// -----BEGIN RSA PRIVATE KEY-----
/// ...
/// -----END RSA PRIVATE KEY-----
/// </summary>
/// <param name="privateKey">Pkcs1格式私鑰</param>
/// <returns></returns>
public static AsymmetricKeyParameter GetAsymmetricKeyParameterFormPrivateKey(string privateKey)
{
    if (string.IsNullOrEmpty(privateKey))
    {
        throw new ArgumentNullException(nameof(privateKey));
    }

    var instance = RsaPrivateKeyStructure.GetInstance(Base64.Decode(privateKey));
    return new RsaPrivateCrtKeyParameters(instance.Modulus, instance.PublicExponent, instance.PrivateExponent,instance.Prime1, instance.Prime2, instance.Exponent1, instance.Exponent2, instance.Coefficient);
}

/// <summary>
/// -----BEGIN PRIVATE KEY-----
/// ...
/// -----END PRIVATE KEY-----
/// </summary>
/// <param name="privateKey">Pkcs8格式私鑰</param>
/// <returns></returns>
public static AsymmetricKeyParameter GetAsymmetricKeyParameterFormAsn1PrivateKey(string privateKey)
{
    return PrivateKeyFactory.CreateKey(Base64.Decode(privateKey));
}

/// <summary>
/// -----BEGIN PUBLIC KEY-----
/// ...
/// -----END PUBLIC KEY-----
/// </summary>
/// <param name="publicKey">公鑰</param>
/// <returns></returns>
public static AsymmetricKeyParameter GetAsymmetricKeyParameterFormPublicKey(string publicKey)
{
    if (string.IsNullOrEmpty(publicKey))
    {
        throw new ArgumentNullException(nameof(publicKey));
    }

    return PublicKeyFactory.CreateKey(Base64.Decode(publicKey));
}

RSA加解與解密

 /// <summary>
 /// RSA加密
 /// </summary>
 /// <param name="data">未加密數據字節數組</param>
 /// <param name="parameters">非對稱密鑰參數</param>
 /// <param name="algorithm">密文算法</param>
 /// <returns>已加密數據字節數組</returns>
 public static byte[] Encrypt(byte[] data, AsymmetricKeyParameter parameters, string algorithm)
 {
     if (data == null)
     {
         throw new ArgumentNullException(nameof(data));
     }
     if (parameters == null)
     {
         throw new ArgumentNullException(nameof(parameters));
     }
     if (string.IsNullOrEmpty(algorithm))
     {
         throw new ArgumentNullException(nameof(algorithm));
     }

     var bufferedCipher = CipherUtilities.GetCipher(algorithm);
     bufferedCipher.Init(true, parameters);
     return bufferedCipher.DoFinal(data);
 }

 /// <summary>
 /// RSA解密
 /// </summary>
 /// <param name="data">已加密數據字節數組</param>
 /// <param name="parameters">非對稱密鑰參數</param>
 /// <param name="algorithm">密文算法</param>
 /// <returns>未加密數據字節數組</returns>
 public static byte[] Decrypt(byte[] data, AsymmetricKeyParameter parameters, string algorithm)
{
    if (data == null)
    {
        throw new ArgumentNullException(nameof(data));
    }
    if (parameters == null)
    {
        throw new ArgumentNullException(nameof(parameters));
    }
    if (string.IsNullOrEmpty(algorithm))
    {
        throw new ArgumentNullException(nameof(algorithm));
    }
    var bufferedCipher = CipherUtilities.GetCipher(algorithm);
    bufferedCipher.Init(false, parameters);
    return bufferedCipher.DoFinal(data);
}

 /// <summary>
 /// RSA加密——Base64
 /// </summary>
 /// <param name="data">未加密字符串</param>
 /// <param name="parameters">非對稱密鑰參數</param>
 /// <param name="algorithm">密文算法</param>
 /// <returns>已加密Base64字符串</returns>
 public static string EncryptToBase64(string data, AsymmetricKeyParameter parameters, string algorithm)
 {
     return Base64.ToBase64String(Encrypt(Encoding.UTF8.GetBytes(data), parameters, algorithm));
 }

 /// <summary>
 /// RSA解密——Base64
 /// </summary>
 /// <param name="data">已加密Base64字符串</param>
 /// <param name="parameters">非對稱密鑰參數</param>
 /// <param name="algorithm">密文算法</param>
 /// <returns>未加密字符串</returns>
 public static string DecryptFromBase64(string data, AsymmetricKeyParameter parameters, string algorithm)
 {
     return Encoding.UTF8.GetString(Decrypt(Base64.Decode(data), parameters, algorithm));
 }

/// <summary>
/// RSA加密——十六進制
/// </summary>
/// <param name="data">未加密字符串</param>
/// <param name="parameters">非對稱密鑰參數</param>
/// <param name="algorithm">密文算法</param>
/// <returns>已加密十六進制字符串</returns>
public static string EncryptToHex(string data, AsymmetricKeyParameter parameters, string algorithm)
{
    return Hex.ToHexString(Encrypt(Encoding.UTF8.GetBytes(data), parameters, algorithm));
}

///  <summary>
/// RSA解密——十六進制
/// </summary>
/// <param name="data">已加密十六進制字符串</param>
/// <param name="parameters">非對稱密鑰參數</param>
/// <param name="algorithm">密文算法</param>
/// <returns>未加密字符串</returns>
public static string DecryptFromHex(string data, AsymmetricKeyParameter parameters, string algorithm)
{
    return Encoding.UTF8.GetString(Decrypt(Hex.Decode(data), parameters, algorithm));
}

RSA密文算法

public const string RSA_NONE_NoPadding = "RSA/NONE/NoPadding";
public const string RSA_NONE_PKCS1Padding = "RSA/NONE/PKCS1Padding";
public const string RSA_NONE_OAEPPadding = "RSA/NONE/OAEPPadding";
public const string RSA_NONE_OAEPWithSHA1AndMGF1Padding = "RSA/NONE/OAEPWithSHA1AndMGF1Padding";
public const string RSA_NONE_OAEPWithSHA224AndMGF1Padding = "RSA/NONE/OAEPWithSHA224AndMGF1Padding";
public const string RSA_NONE_OAEPWithSHA256AndMGF1Padding = "RSA/NONE/OAEPWithSHA256AndMGF1Padding";
public const string RSA_NONE_OAEPWithSHA384AndMGF1Padding = "RSA/NONE/OAEPWithSHA384AndMGF1Padding";
public const string RSA_NONE_OAEPWithMD5AndMGF1Padding = "RSA/NONE/OAEPWithMD5AndMGF1Padding";

public const string RSA_ECB_NoPadding = "RSA/ECB/NoPadding";
public const string RSA_ECB_PKCS1Padding = "RSA/ECB/PKCS1Padding";
public const string RSA_ECB_OAEPPadding = "RSA/ECB/OAEPPadding";
public const string RSA_ECB_OAEPWithSHA1AndMGF1Padding = "RSA/ECB/OAEPWithSHA1AndMGF1Padding";
public const string RSA_ECB_OAEPWithSHA224AndMGF1Padding = "RSA/ECB/OAEPWithSHA224AndMGF1Padding";
public const string RSA_ECB_OAEPWithSHA256AndMGF1Padding = "RSA/ECB/OAEPWithSHA256AndMGF1Padding";
public const string RSA_ECB_OAEPWithSHA384AndMGF1Padding = "RSA/ECB/OAEPWithSHA384AndMGF1Padding";
public const string RSA_ECB_OAEPWithMD5AndMGF1Padding = "RSA/ECB/OAEPWithMD5AndMGF1Padding";

......

編碼算法

大家要明白,不管是對稱算法還是非對稱算法,其輸入與輸出均是字節數組,通常我們要結合編碼算法對加密之後或解密之前的數據,進行編碼操作。

BouncyCastle提供的Base64編碼算法

namespace Org.BouncyCastle.Utilities.Encoders
{
    public sealed class Base64
    {
        //
        public static byte[] Decode(byte[] data);
        //
        public static byte[] Decode(string data);
        //
        public static int Decode(string data, Stream outStream);
        //
        public static byte[] Encode(byte[] data);
        //
        public static byte[] Encode(byte[] data, int off, int length);
        //
        public static int Encode(byte[] data, Stream outStream);
        //
        public static int Encode(byte[] data, int off, int length, Stream outStream);
        public static string ToBase64String(byte[] data);
        public static string ToBase64String(byte[] data, int off, int length);
    }
}

BouncyCastle提供的Hex十六進制編碼算法

namespace Org.BouncyCastle.Utilities.Encoders
{
    //
    // 摘要:
    //     Class to decode and encode Hex.
    public sealed class Hex
    {
        //
        public static byte[] Decode(byte[] data);
        //
        public static byte[] Decode(string data);
        //
        public static int Decode(string data, Stream outStream);
        //
        public static byte[] Encode(byte[] data);
        //
        public static byte[] Encode(byte[] data, int off, int length);
        //
        public static int Encode(byte[] data, Stream outStream);
        //
        public static int Encode(byte[] data, int off, int length, Stream outStream);
        public static string ToHexString(byte[] data);
        public static string ToHexString(byte[] data, int off, int length);
    }
}

RSA加解密示例

private static void RSA_ECB_PKCS1Padding()
 {
     var data = "hello rsa";

     Console.WriteLine($"加密原文:{data}");

     // rsa pkcs8 private key encrypt
     //algorithm  rsa/ecb/pkcs1padding
     var pkcs8data = RSA.EncryptToBase64(data, RSAUtilities.GetAsymmetricKeyParameterFormAsn1PrivateKey(pkcs8_1024_private_key),CipherAlgorithms.RSA_ECB_PKCS1Padding);

     Console.WriteLine("密鑰格式:pkcs8,密文算法:rsa/ecb/pkcs1padding,加密結果");
     Console.WriteLine(pkcs8data);

     //rsa pkcs1 private key encrypt
     //algorithm  rsa/ecb/pkcs1padding
     var pkcs1data = RSA.EncryptToBase64(data, RSAUtilities.GetAsymmetricKeyParameterFormPrivateKey(pkcs1_1024_private_key),CipherAlgorithms.RSA_ECB_PKCS1Padding);

     Console.WriteLine($"密鑰格式:pkcs1,密文算法:rsa/ecb/pkcs1padding");
     Console.WriteLine(pkcs1data);

     Console.WriteLine($"加密結果比對是否一致:{pkcs8data.Equals(pkcs1data)}");

     var _1024_public_key = RSAKeyConverter.GetPublicKeyFromPrivateKeyPkcs1(pkcs1_1024_private_key);

     Console.WriteLine($"從pkcs1私鑰中提取公鑰:");
     Console.WriteLine(_1024_public_key);

     Console.WriteLine("使用公鑰解密數據:");
     //rsa public key decrypt
     //algorithm  rsa/ecb/pkcs1padding
     Console.WriteLine(RSA.DecryptFromBase64(pkcs1data, RSAUtilities.GetAsymmetricKeyParameterFormPublicKey(_1024_public_key),CipherAlgorithms.RSA_ECB_PKCS1Padding));

     Console.WriteLine();
 }

下期預告

下一篇將介紹哈希算法(HMACSHA1、HMACSHA256、SHA1、SHA1WithRSA、SHA256、SHA256WithRSA),敬請期待…

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

【其他文章推薦】

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

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

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

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

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

寫一個擴展性較強的搜索主頁

前置

  • 點擊按鈕切換搜索引擎
  • 搜索框跟隨切換改變樣式
  • 使用 vue 最快了

template

為了方便擴展,使用 v-for 循環渲染出按鈕,綁定切換搜索引擎的 method , 傳入不同名稱以區別搜索引擎。按鈕的樣式也動態綁定。

輸入框動態綁定樣式,在點擊按鈕切換搜索引擎時,搜索框綁定的樣式對應的 data 改變。

<template>
  <section id="search-wrapper">
    <el-row class="search-wrapper-row">
      <el-row style="margin-bottom: 10px">
        <el-button
          size="mini"
          type="primary"
          v-for="(item, index) in source"
          @click="changeSource(item.name)"
          :key="index"
          :style="
                        `background:${item.color};border-color:${item.color}`
                    "
          >{{ item.name }}</el-button
        >
      </el-row>
      <el-input :placeholder="searchbarStyle.placeholder" :class="searchbarStyle.className" v-model="searchValue" clearable>
        <el-button @click="submit" slot="append" icon="el-icon-search"></el-button>
      </el-input>
    </el-row>
  </section>
</template>

script

data

  • baseUrl 搜索引擎地址
  • searchValue input v-model 綁定的搜索內容
  • searchbarStyle 搜索框對應的樣式,值類型為 Object, 方便擴展不同搜索框樣式
  • source 按鈕的樣式即名稱,數組對象, 方便按鈕擴展

methods

changeSource 點擊按鈕時觸發,接收搜索引擎 name, 內部使用 Map,匹配對應的函數,在函數中更改 baseUrl 和 searchbarStyle,由於在 template 動態綁定了 searchbarStyle,這樣就能根據所選擇的搜索類型改變搜索框的樣式了。

代碼塊較長,我將它摺疊

export default {
  data() {
    return {
      baseUrl: 'https://www.baidu.com/s?ie=UTF-8&wd=',
      searchValue: '',
      searchbarStyle: {
        className: 'baidu',
        placeholder: '百度一下,你就知道',
      },
      source: [
        {
          name: '百度',
          color: '#2932E1',
        },
        {
          name: '必應',
          color: '#0c8484',
        },
        {
          name: '搜狗',
          color: '#FF6F17',
        },
        {
          name: '谷歌',
          color: '#4285F4',
        },
        {
          name: 'NPM',
          color: '#EA4335',
        },
      ],
    }
  },
  methods: {
    changeSource(name) {
      const actions = new Map([
        [
          '百度',
          () => {
            this.baseUrl = 'https://www.baidu.com/s?ie=UTF-8&wd='
            this.searchbarStyle = {
              className: 'baidu',
              placeholder: '百度一下,你就知道',
            }
          },
        ],
        [
          '必應',
          () => {
            this.baseUrl = 'https://cn.bing.com/search?FORM=BESBTB&q='
            this.searchbarStyle = {
              className: 'bing',
              placeholder: '必應搜索',
            }
          },
        ],
        [
          '搜狗',
          () => {
            this.baseUrl = 'https://www.sogou.com/web?query='
            this.searchbarStyle = {
              className: 'sougou',
              placeholder: '搜狗搜索',
            }
          },
        ],
        [
          '谷歌',
          () => {
            this.baseUrl = 'https://www.google.com/search?q='
            this.searchbarStyle = {
              className: 'google',
              placeholder: 'Google Search',
            }
          },
        ],
        [
          'NPM',
          () => {
            this.baseUrl = 'https://www.npmjs.com/search?q='
            this.searchbarStyle = {
              className: 'npm',
              placeholder: 'Search Packages',
            }
          },
        ],
      ])
      actions.get(name)()
    },
    submit() {
      const url = this.baseUrl + this.searchValue
      window.open(url)
    },
  },
}

style

在 searchbarStyle 對象中有個 className 字段,input 會動態綁定與之對應的 css class。比如選擇百度時對應 .baidu, 選擇必應時對應 .bing etc. 由於使用了 scss 預處理器,通過 @each 循環它們就好了。

$sources-color: (
  baidu: #2932e1,
  bing: #0c8484,
  sougou: #ff6f17,
  google: #4285f4,
  npm: #ea4335,
);

$source-list: baidu bing sougou google npm;

@each $source in $source-list {
  .#{$source} {
    .el-input-group__append,
    input {
      border-color: map-get($sources-color, $source);
      &:hover {
        border-color: map-get($sources-color, $source);
      }
    }
    .el-icon-search {
      color: map-get($sources-color, $source);
      &:hover {
        border-color: map-get($sources-color, $source);
      }
    }
  }
}

最後

搜索引擎在搜索時並不是簡單的 baseUrl + 搜索內容的形式,url 中還攜帶了其他參數。

數據可以單獨抽離, 使用 export 導出並引入, 這樣 .vue 看起來不會太長,易於維護。

可以綁定按下 enter 時發起搜索。

預覽地址

如果你有建議歡迎指教,謝謝

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

SpringBoot後端系統的基礎架構

前言

前段時間完成了畢業設計課題——《基於Spring Boot + Vue的直播後台管理系統》,項目名為LBMS,主要完成了對直播平台數據的可視化展示和分級的權限管理。雖然相當順利地通過了答辯,但是由於時間以及本人水平的不足,其實後端系統的代碼還僅僅停留在“能跑就行”。因此這篇文章主要也是為了反思一下項目中亟待完善的地方,我後續也會考慮在此基礎上編寫一個後端管理系統的通用架構模板。

2020/6/10 這個模板項目已經在做了:common-MS
2020/6/12 完成了日誌處理、異常處理、結果封裝、參數校驗模塊

日誌處理

日誌框架

Java中可用的日誌框架有很多,並且通常都有着抽象層+實現層的結構,在實際應用中,只需要考慮抽象層提供的功能接口而不用了解實現層的具體結構。Spring Boot默認的日誌框架為Slf4j + logback。在我的畢設項目中,雖然引入了日誌框架,但是卻很少使用。

Slf4j的輸出級別有5種:trace、debug、info、warn、error,可以通過在properties或yml文件中通過logging.level.root參數指定日誌輸出的級別,其中root代表配置對整個項目生效,可以修改為其他路徑進行自定義配置

日誌代碼的簡化

使用lombok可以簡化代碼的編寫:

Logger logger = LoggerFactory.getLogger(MyLog.class);
logger.info("logger info test");
@Slf4j
// ...
log.info("lombok info test")

對於日誌信息中的變量,建議使用佔位符形式而非字符串拼接

log.info(time + " " + methodName + "is invoked");
log.info("{} {} is invoked", time, methodName)

將日誌輸出到文件

這裏用了某位大牛寫的logback-spring.xml進行配置(可以訪問我的Github獲取具體文件),配置完成后可以將日誌按級別的不同輸出到指定目錄下的不同文件,並且對每天的日誌分開保存,日誌文件大小超過100MB時,還可以自動分塊。

基於AOP的日誌處理

之前用DRF做一個項目時,發現它很貼心地在控制台展示了每個請求的參數、返回狀態碼等信息,SpringBoot當然也可以實現類似的功能。

想要實現上述需求,毫無疑問要在Controller層使用AOP了。對每個請求,我想要輸出對應的URL、請求方法、參數、返回狀態碼等信息。

AOP的切點切面:

@Pointcut("execution(* priv.zzz.controller..*.*(..))")
public void controllerAspect() {}

@Before("controllerAspect()")
public void before(JoinPoint joinPoint){
    log.info(getRequestMessage(joinPoint));
}

@AfterReturning(pointcut = "controllerAspect()", returning = "returnValue")
public void after(JoinPoint joinPoint, Object returnValue){
    if (returnValue instanceof Result){
            log.info(getResponseMessage(joinPoint, ((Result) returnValue).getStatus()));
    }
    if (returnValue instanceof ResultSet){
        log.info(getResponseMessage(joinPoint, ((ResultSet) returnValue).getStatus()));
    }
}

URL、rquestMethod:

private String getBaseMessage(JoinPoint joinPoint) {

    HttpServletRequest request = ((ServletRequestAttributes)(Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))).getRequest();
    String url = request.getRequestURI();
    String requestMethod = request.getMethod();
    String datetime = DateFormatter.format(new Date());

    return datetime + " " + url + " " + requestMethod;
}

請求參數:

private String getRequestMessage(JoinPoint joinPoint) {
    MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
    Object[] args = joinPoint.getArgs();
    String[] parameters = methodSignature.getParameterNames();

    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < Math.min(args.length, parameters.length); i++){
        stringBuilder.append(parameters[i]).append(":").append(args[i]).append(" ");
    }
    String params = "{ "+stringBuilder.toString()+"}";

    return this.getBaseMessage(joinPoint) + " " + params;
}
private String getResponseMessage(JoinPoint joinPoint, int status) {
    return this.getBaseMessage(joinPoint) + " " + status;
}

最終效果:

2020-06-11 13:10:32 /log GET { name:test number:1 }
2020-06-11 13:10:32 /log GET 200

結果封裝

前後端分離的情況下前後端一般都是通過Json數據進行交互,使用@RestController註解可以將返回的對象轉為Json格式,在那之前,我們需要對返回的結果封裝為Result對象。Result中主要要包含的字段有status、message和data,對於status和message,我使用枚舉類型ResultCode進行封裝,其中包含SUCCESS、NOT_FOUND、UNAUTHORIZED等常見狀態碼。data要考慮返回的數據是否是一個列表,如果是列表,還需要實現分頁功能。

在LBMS中,我將這兩種結果集(單個對象和列表對象)封裝為同一個結果集,在新的模板項目中,我嘗試使用Result和ResultSet兩種結果集進行封裝。這樣做的好處是返回結果更加清晰,缺點是有些地方可能需要一些額外的處理,比如在日誌模塊獲取controller返回的狀態碼時,具體的優劣有待更加深入的使用。

Result示例:

{
  "timestamp": "2020-06-12T15:44:02.106+08:00",
  "status": 200,
  "message": "success",
  "data": 123,
  "path": "/result"
}

ResultSet示例:

{
  "timestamp": "2020-06-12T15:38:01.130+08:00",
  "total": 2,
  "status": 200,
  "message": "success",
  "list": [
    {
      "username": "Alice",
      "age": 20,
      "sex": 0,
      "email": "12345@qq.com"
    },
    {
      "username": "Eric",
      "age": 21,
      "sex": 1,
      "email": "12345@163.com"
    }
  ],
  "path": "/result/set"
}

結果封裝還要考慮的一個問題是對異常的處理,這個我在異常處理章節會談到。

參數校驗

上一個項目中的參數校驗做的相當有限,目前Spring Boot主流的參數校驗方式有hibernate-validator、Assert等。使用validator參數校驗的位置可以在實體類字段處,也可以在Controller傳參處。

網上大部分文章說spring-boot-starter-web已經包含了hibernate-validator,但我不知道為什麼無法直接使用@NotNull等註解,因此手動引入validator:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version>
</dependency>

一個簡單的例子:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TestUser {

    @NotNull(message = "用戶名不能為空")
    @NotBlank(message = "用戶名不能為空")
    @Length(max = 20, message = "用戶名過長")
    private String username;

    @Min(0)
    private Integer age;

    @Range(min = 0, max = 1)
    private Integer sex;

    @Email(message = "郵箱格式錯誤")
    private String email;

}

使用Assert進行校驗:

Assert.notNull(user.getUsername(), "用戶名不能為空");

validator校驗失敗時,會拋出MethodArgumentNotValidException異常。

Assert校驗失敗時會拋出IllegalArgumentException。

實際應用中我們可以靈活使用這兩種校驗方式,並且可以通過ExceptionHandler對這些異常進行捕獲和統一處理。

異常處理

LBMS中,我的異常處理採用的是自定義異常+@ResponseStatus註解的方式,在特定的地方拋出異常,交給ResponseStatusExceptionResolver去處理。

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "無法識別的操作")
public class BadOperationException extends Exception {

    public BadOperationException(){
        super();
    }

    public BadOperationException(String msg){
        super(msg);
    }
}

在common-MS中,異常處理採用@ControllerAdvice+@ExceptionHandler實現,@ControllerAdvice將一個類標註為全局的異常處理類,@ExceptionHandler用於捕獲不同的異常進行對應處理。同理,對於異常的返回結果也與正常返回結果格式保持一致,使用Result封裝。

例如,捕獲上述validator拋出的MethodArgumentNotValidException異常並進行處理的代碼為:

@ExceptionHandler(value = { MethodArgumentNotValidException.class })
public Result<String> validatorException(HttpServletResponse response, MethodArgumentNotValidException e) {
    // validator設置了message時返回message,未設置則返回“非法參數”
    FieldError error = e.getBindingResult().getFieldError();
    String message = "非法參數";
    if(error != null){
        message = error.getField() + error.getDefaultMessage();
    }
    response.setStatus(400);
    return Result.failure(400, message);
}

當提交的郵箱格式錯誤時返回:

{
  "timestamp": "2020-06-12T15:45:07.874+08:00",
  "status": 400,
  "message": "email郵箱格式錯誤",
  "data": null,
  "path": "/user"
}

同理,還可以對自定義的異常進行處理:

public class ExampleException extends Exception{

    public ExampleException() {super();}

    public ExampleException(String message) {
        super(message);
    }
}

使用時直接拋出異常即可:

@RequestMapping(value = "exception", method = RequestMethod.GET)
public Result exampleException() throws ExampleException {
    throw new ExampleException("這是一個測試異常");
}

如果需要修改Response的狀態碼而不僅僅是使用自定義的status,可以在@ExceptionHandler註解的方法內引入並使用

response.setStatus(400);

待續~

todo:Shiro、分頁功能、Redis等。

完整代碼移步Github:common-MS

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

【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

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

信息泄漏時代,如何讓自己的密碼更安全?

密碼的重要性,相信大家都不言而喻。而密碼泄漏或信息泄漏,經常是層出不窮地出現,令人防不勝防。所以,一個強大而複雜的密碼是保證自己賬戶安全的第一步。

為了防止信息泄漏,我們可以做些什麼呢?

  • 密碼足夠複雜;
  • 每個平台密碼都不一樣,比如QQ,微信,郵箱等;
  • 定期更換密碼。

那怎樣的密碼才算是比較可靠的密碼?一般而言,一個密碼至少12位字符,包含数字,包含大小寫,包含特殊符號,不使用現有單詞,即是一個比較複雜的密碼。

那你自認為比較安全的密碼,是否真正的安全呢?這裏良許介紹兩個工具可以用來評估你的密碼的安全性—— cracklib 和 pwscore 。

cracklib介紹

1. cracklib 的安裝

cracklib 可以用來檢測你的密碼是否可靠,在大部分發行版里都可以直接安裝這個工具。

對於 Fedora 系的發行版,可以使用 dnf 命令安裝 cracklib:

$ sudo dnf install cracklib

對於 Debian/Ubuntu 系的發行版,可以使用 apt-get 命令安裝:

$ sudo apt install libcrack2

對於 Arch 系統的發行版,可以使用 pacman 命令安裝:

$ sudo pacman -S cracklib

對於 RHEL/CentOS 系的發行版,可以使用 yum 命令安裝:

$ sudo yum install cracklib

對於 openSUSE 系的發行版,可以使用 zypper 命令安裝:

$ sudo zypper install cracklib

2. cracklib 的使用

我們直接來看幾個實例。

如果你的密碼里包含了人名、地名,或者我們常用的單詞,那麼會被提示 it is based on a dictionary word :

$ echo "password" | cracklib-check
password: it is based on a dictionary word

Linux 下默認的密碼長度是 7 個字符,如果你的密碼長度小於 7 個字符,會被提示 it is WAY too short :

$ echo "123" | cracklib-check 
123: it is WAY too short

如果你的密碼比較強壯,則會提示 OK :

$ echo "ME$2w!@fgty6723" | cracklib-check
ME!@fgty6723: OK

pwscore 介紹

我們使用 cracklib 工具只能判斷一個密碼是否安全,但具體也不知道它有多安全。而 pwscore 工具就能告訴你,你的密碼強度可以打幾分。

1. pwscore 的安裝

同樣地,對於大部分 Linux 發行版,可以直接安裝 pwscore 工具。安裝過程與 cracklib 類似,只需將 cracklib 改成 pwscore 即可。這裏介紹 Debian/Ubuntu 系發行版的安裝,其餘的類似:

$ sudo apt install libpwquality

2. pwscore 的使用

同樣直接來看幾個實例。

與 cracklib 類似,如果你的密碼里包含了人名、地名,或者我們常用的單詞,那麼會被提示 it is based on a dictionary word ;如果密碼長度短於 7 個字符,會被提示 it is WAY too short 。

$ echo "password" | pwscore
Password quality check failed:
 The password fails the dictionary check - it is based on a dictionary word

$ echo "123" | pwscore
Password quality check failed:
 The password is shorter than 8 characters

如果你的密碼合乎規範,那麼它就會給你打相應的分數:

$ echo "ME!@fgty6723" | pwscore
90

小結

雖然黑客有一萬種竊取你的私人數據的方法,但一個強壯的密碼是你保護你敏感數據的第一步。網絡環境本身就不是 100% 安全,如果你再使用一個很容易玫破的密碼,那下一個艷照門可能很快就會再次出現……

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

附022.Kubernetes_v1.18.3高可用部署架構一

kubeadm介紹

kubeadm概述

參考附003.Kubeadm部署Kubernetes。

kubeadm功能

參考附003.Kubeadm部署Kubernetes。

本方案描述

  • 本方案採用kubeadm部署Kubernetes 1.18.3版本;
  • etcd採用混部方式;
  • Keepalived:實現VIP高可用;
  • Nginx:以Pod形式運行與Kubernetes之上,即in Kubernetes模式,提供反向代理至3個master 6443端口;
  • 其他主要部署組件包括:
    • Metrics:度量;
    • Dashboard:Kubernetes 圖形UI界面;
    • Helm:Kubernetes Helm包管理工具;
    • Ingress:Kubernetes 服務暴露;
    • Longhorn:Kubernetes 動態存儲組件。

部署規劃

節點規劃

節點主機名 IP 類型 運行服務
master01 172.24.8.71 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、kubelet、metrics、calico
master02 172.24.8.72 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、kubelet、metrics、calico
master03 172.24.8.73 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、kubelet、metrics、calico
worker01 172.24.8.74 Kubernetes worker節點 docker、kubelet、proxy、calico
worker02 172.24.8.75 Kubernetes worker節點 docker、kubelet、proxy、calico
worker03 172.24.8.76 Kubernetes worker節點 docker、kubelet、proxy、calico

Kubernetes的高可用主要指的是控制平面的高可用,即指多套Master節點組件和Etcd組件,工作節點通過負載均衡連接到各Master。

Kubernetes高可用架構中etcd與Master節點組件混布方式特點:

  • Etcd混布方式
  • 所需機器資源少
  • 部署簡單,利於管理
  • 容易進行橫向擴展
  • 風險大,一台宿主機掛了,master和etcd就都少了一套,集群冗餘度受到的影響比較大。

提示:本實驗使用Keepalived+Nginx架構實現Kubernetes的高可用。

初始準備

[root@master01 ~]# hostnamectl set-hostname master01 #其他節點依次修改

[root@master01 ~]# cat >> /etc/hosts << EOF
172.24.8.71 master01··
172.24.8.72 master02
172.24.8.73 master03
172.24.8.74 worker01
172.24.8.75 worker02
172.24.8.76 worker03
EOF

[root@master01 ~]# vi k8sinit.sh

#!/bin/sh
#****************************************************************#
# ScriptName: k8sinit.sh
# Author: xhy
# Create Date: 2020-05-30 16:30
# Modify Author: xhy
# Modify Date: 2020-05-30 16:30
# Version: 
#***************************************************************#
# Initialize the machine. This needs to be executed on every machine.

# Add docker user
useradd -m docker

# Disable the SELinux.
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

# Turn off and disable the firewalld.
systemctl stop firewalld
systemctl disable firewalld

# Modify related kernel parameters & Disable the swap.
cat > /etc/sysctl.d/k8s.conf << EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.tcp_tw_recycle = 0
vm.swappiness = 0
vm.overcommit_memory = 1
vm.panic_on_oom = 0
net.ipv6.conf.all.disable_ipv6 = 1
EOF
sysctl -p /etc/sysctl.d/k8s.conf >&/dev/null
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
modprobe br_netfilter

# Add ipvs modules
cat > /etc/sysconfig/modules/ipvs.modules <<EOF
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
modprobe -- nf_conntrack
EOF

chmod 755 /etc/sysconfig/modules/ipvs.modules
bash /etc/sysconfig/modules/ipvs.modules

# Install rpm
yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget

# Install Docker Compose
sudo curl -L "http://down.linuxsb.com:8888/docker/compose/releases/download/1.25.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Update kernel
rpm --import http://down.linuxsb.com:8888/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://down.linuxsb.com:8888/elrepo-release-7.0-4.el7.elrepo.noarch.rpm
yum --disablerepo="*" --enablerepo="elrepo-kernel" install -y kernel-ml
sed -i 's/^GRUB_DEFAULT=.*/GRUB_DEFAULT=0/' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
yum update -y

# Reboot the machine.
# reboot

提示:對於某些特性,可能需要升級內核,內核升級操作見《018.Linux升級內核》。4.19版及以上內核nf_conntrack_ipv4已經改為nf_conntrack。

互信配置

為了更方便遠程分發文件和執行命令,本實驗配置master節點到其它節點的 ssh 信任關係。

[root@master01 ~]# ssh-keygen -f ~/.ssh/id_rsa -N ''
[root@master01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@master01
[root@master01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@master02
[root@master01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@master03
[root@master01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@worker01
[root@master01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@worker02
[root@master01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@worker03

提示:此操作僅需要在master節點操作。

其他準備

[root@master01 ~]# vi environment.sh

#!/bin/sh
#****************************************************************#
# ScriptName: environment.sh
# Author: xhy
# Create Date: 2020-05-30 16:30
# Modify Author: xhy
# Modify Date: 2020-05-30 16:30
# Version: 
#***************************************************************#
# 集群 MASTER 機器 IP 數組
export MASTER_IPS=(172.24.8.71 172.24.8.72 172.24.8.73)

# 集群 MASTER IP 對應的主機名數組
export MASTER_NAMES=(master01 master02 master03)

# 集群 NODE 機器 IP 數組
export NODE_IPS=(172.24.8.74 172.24.8.75 172.24.8.76)

# 集群 NODE IP 對應的主機名數組
export NODE_NAMES=(worker01 worker02 worker03)

# 集群所有機器 IP 數組
export ALL_IPS=(172.24.8.71 172.24.8.72 172.24.8.73 172.24.8.74 172.24.8.75 172.24.8.76)

# 集群所有IP 對應的主機名數組
export ALL_NAMES=(master01 master02 master03 worker01 worker02 worker03)
[root@master01 ~]# source environment.sh
[root@master01 ~]# chmod +x *.sh
[root@master01 ~]# for all_ip in ${ALL_IPS[@]}
  do
    echo ">>> ${all_ip}"
    scp -rp /etc/hosts root@${all_ip}:/etc/hosts
    scp -rp k8sinit.sh root@${all_ip}:/root/
    ssh root@${all_ip} "bash /root/k8sinit.sh"
  done

集群部署

Docker安裝

[root@master01 ~]# for all_ip in ${ALL_IPS[@]}
  do
    echo ">>> ${all_ip}"
    ssh root@${all_ip} "yum -y install yum-utils device-mapper-persistent-data lvm2"
    ssh root@${all_ip} "yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo"
    ssh root@${all_ip} "yum -y install docker-ce"
    ssh root@${all_ip} "mkdir /etc/docker"
    ssh root@${all_ip} "cat > /etc/docker/daemon.json <<EOF
{
  \"registry-mirrors\": [\"https://dbzucv6w.mirror.aliyuncs.com\"],
  \"exec-opts\": [\"native.cgroupdriver=systemd\"],
  \"log-driver\": \"json-file\",
  \"log-opts\": {
    \"max-size\": \"100m\"
  },
  \"storage-driver\": \"overlay2\",
  \"storage-opts\": [
    \"overlay2.override_kernel_check=true\"
  ]
}
EOF"
    ssh root@${all_ip} "systemctl restart docker"
    ssh root@${all_ip} "systemctl enable docker"
    ssh root@${all_ip} "systemctl status docker"
    ssh root@${all_ip} "iptables -nvL"
  done

提示:如上僅需Master01節點操作,從而實現所有節點自動化安裝。

相關組件包

需要在每台機器上都安裝以下的軟件包:

  • kubeadm: 用來初始化集群的指令;
  • kubelet: 在集群中的每個節點上用來啟動 pod 和 container 等;
  • kubectl: 用來與集群通信的命令行工具。

kubeadm不能安裝或管理 kubelet 或 kubectl ,所以得保證他們滿足通過 kubeadm 安裝的 Kubernetes控制層對版本的要求。如果版本沒有滿足要求,可能導致一些意外錯誤或問題。
具體相關組件安裝見;附001.kubectl介紹及使用書

提示:Kubernetes 1.18版本所有兼容相應組件的版本參考:https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.18.md。

正式安裝

[root@master01 ~]# for all_ip in ${ALL_IPS[@]}
  do
    echo ">>> ${all_ip}"
    ssh root@${all_ip} "cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF"
    ssh root@${all_ip} "yum install -y kubeadm-1.18.3-0.x86_64 kubelet-1.18.3-0.x86_64 kubectl-1.18.3-0.x86_64 --disableexcludes=kubernetes"
    ssh root@${all_ip} "systemctl enable kubelet"
done

[root@master01 ~]# yum search -y kubelet --showduplicates #查看相應版本

提示:如上僅需Master01節點操作,從而實現所有節點自動化安裝,同時此時不需要啟動kubelet,初始化的過程中會自動啟動的,如果此時啟動了會出現報錯,忽略即可。

說明:同時安裝了cri-tools, kubernetes-cni, socat三個依賴: socat:kubelet的依賴; cri-tools:即CRI(Container Runtime Interface)容器運行時接口的命令行工具。

部署高可用組件I

Keepalived安裝

[root@master01 ~]# for master_ip in ${MASTER_IPS[@]}
  do
    echo ">>> ${master_ip}"
    ssh root@${master_ip} "yum -y install gcc gcc-c++ make libnl libnl-devel libnfnetlink-devel openssl-devel"
    ssh root@${master_ip} "wget http://down.linuxsb.com:8888/software/keepalived-2.0.20.tar.gz"
    ssh root@${master_ip} "tar -zxvf keepalived-2.0.20.tar.gz"
    ssh root@${master_ip} "cd keepalived-2.0.20/ && ./configure --sysconf=/etc --prefix=/usr/local/keepalived && make && make install"
    ssh root@${master_ip} "systemctl enable keepalived && systemctl start keepalived"
  done

提示:如上僅需Master01節點操作,從而實現所有節點自動化安裝。

創建配置文件

[root@master01 ~]# wget http://down.linuxsb.com:8888/ngkek8s.sh		#拉取自動部署腳本
[root@master01 ~]# chmod u+x ngkek8s.sh
[root@master01 ~]# vi ngkek8s.sh
#!/bin/sh
#****************************************************************#
# ScriptName: k8s_ha.sh
# Author: xhy
# Create Date: 2020-05-13 16:32
# Modify Author: xhy
# Modify Date: 2020-06-12 12:53
# Version: v2
#***************************************************************#

#######################################
# set variables below to create the config files, all files will create at ./config directory
#######################################

# master keepalived virtual ip address
export K8SHA_VIP=172.24.8.100

# master01 ip address
export K8SHA_IP1=172.24.8.71

# master02 ip address
export K8SHA_IP2=172.24.8.72

# master03 ip address
export K8SHA_IP3=172.24.8.73

# master01 hostname
export K8SHA_HOST1=master01

# master02 hostname
export K8SHA_HOST2=master02

# master03 hostname
export K8SHA_HOST3=master03

# master01 network interface name
export K8SHA_NETINF1=eth0

# master02 network interface name
export K8SHA_NETINF2=eth0

# master03 network interface name
export K8SHA_NETINF3=eth0

# keepalived auth_pass config
export K8SHA_KEEPALIVED_AUTH=412f7dc3bfed32194d1600c483e10ad1d

# kubernetes CIDR pod subnet
export K8SHA_PODCIDR=10.10.0.0

# kubernetes CIDR svc subnet
export K8SHA_SVCCIDR=10.20.0.0

[root@master01 ~]# ./ngkek8s.sh

解釋:如上僅需Master01節點操作。執行ngkek8s.sh腳本后,會自動生成以下配置文件:

  • kubeadm-config.yaml:kubeadm初始化配置文件,位於當前目錄
  • keepalived:keepalived配置文件,位於各個master節點的/etc/keepalived目錄
  • nginx-lb:nginx-lb負載均衡配置文件,位於各個master節點的/etc/kubernetes/nginx-lb/目錄
  • calico.yaml:calico網絡組件部署文件,位於config/calico/目錄
[root@master01 ~]# cat kubeadm-config.yaml		#檢查集群初始化配置
apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
networking:
  serviceSubnet: "10.20.0.0/16"			     	#設置svc網段
  podSubnet: "10.10.0.0/16"			        #設置Pod網段
  dnsDomain: "cluster.local"
kubernetesVersion: "v1.18.3"			    	#設置安裝版本
controlPlaneEndpoint: "172.24.8.100:16443"		#設置相關API VIP地址
apiServer:
  certSANs:
  - master01
  - master02
  - master03
  - 127.0.0.1
  - 172.24.8.71
  - 172.24.8.72
  - 172.24.8.73
  - 172.24.8.100
  timeoutForControlPlane: 4m0s
certificatesDir: "/etc/kubernetes/pki"
imageRepository: "k8s.gcr.io"
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
featureGates:
  SupportIPVSProxyMode: true
mode: ipvs

提示:如上僅需Master01節點操作,更多config文件參考:https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2。
此kubeadm部署初始化配置更多參考:https://pkg.go.dev/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2?tab=doc。

啟動Keepalived

[root@master01 ~]# cat /etc/keepalived/check_apiserver.sh	#確認Keepalived配置
[root@master01 ~]# for master_ip in ${MASTER_IPS[@]}
  do
    echo ">>> ${master_ip}"
    ssh root@${master_ip} "systemctl start keepalived.service && systemctl enable keepalived.service"
    ssh root@${master_ip} "systemctl status keepalived.service | grep Active"
  done
[root@master01 ~]# for all_ip in ${ALL_IPS[@]}
  do
    echo ">>> ${all_ip}"
    ssh root@${all_ip} "ping -c1 172.24.8.100"
  done								#等待10s左右執行檢查

提示:如上僅需Master01節點操作,從而實現所有節點自動啟動服務。

啟動Nginx

執行ngkek8s.sh腳本后,nginx-lb的配置文件會自動複製到各個master的節點的/etc/kubernetes/nginx-lb目錄。

[root@master01 ~]# for master_ip in ${MASTER_IPS[@]}
  do
    echo ">>> ${master_ip}"
    ssh root@${master_ip} "cd /etc/kubernetes/nginx-lb/ && docker-compose up -d"
    ssh root@${master_ip} "docker-compose ps"
  done

提示:如上僅需Master01節點操作,從而實現所有節點自動啟動服務。

初始化集群-Master

拉取鏡像

[root@master01 ~]# kubeadm --kubernetes-version=v1.18.3 config images list #列出所需鏡像

[root@master01 ~]# cat config/downimage.sh			#確認版本,提前下載鏡像
#!/bin/sh
#****************************************************************#
# ScriptName: downimage.sh
# Author: xhy
# Create Date: 2020-05-29 19:55
# Modify Author: xhy
# Modify Date: 2020-05-30 16:07
# Version: v2
#***************************************************************#

KUBE_VERSION=v1.18.3
CALICO_VERSION=v3.14.1
CALICO_URL=calico
KUBE_PAUSE_VERSION=3.2
ETCD_VERSION=3.4.3-0
CORE_DNS_VERSION=1.6.7
GCR_URL=k8s.gcr.io
METRICS_SERVER_VERSION=v0.3.6
INGRESS_VERSION=0.32.0
CSI_PROVISIONER_VERSION=v1.4.0
CSI_NODE_DRIVER_VERSION=v1.2.0
CSI_ATTACHER_VERSION=v2.0.0
CSI_RESIZER_VERSION=v0.3.0 
ALIYUN_URL=registry.cn-hangzhou.aliyuncs.com/google_containers
UCLOUD_URL=uhub.service.ucloud.cn/uxhy
QUAY_URL=quay.io

kubeimages=(kube-proxy:${KUBE_VERSION}
kube-scheduler:${KUBE_VERSION}
kube-controller-manager:${KUBE_VERSION}
kube-apiserver:${KUBE_VERSION}
pause:${KUBE_PAUSE_VERSION}
etcd:${ETCD_VERSION}
coredns:${CORE_DNS_VERSION}
metrics-server-amd64:${METRICS_SERVER_VERSION}
)

for kubeimageName in ${kubeimages[@]} ; do
docker pull $UCLOUD_URL/$kubeimageName
docker tag $UCLOUD_URL/$kubeimageName $GCR_URL/$kubeimageName
docker rmi $UCLOUD_URL/$kubeimageName
done

calimages=(cni:${CALICO_VERSION}
pod2daemon-flexvol:${CALICO_VERSION}
node:${CALICO_VERSION}
kube-controllers:${CALICO_VERSION})

for calimageName in ${calimages[@]} ; do
docker pull $UCLOUD_URL/$calimageName
docker tag $UCLOUD_URL/$calimageName $CALICO_URL/$calimageName
docker rmi $UCLOUD_URL/$calimageName
done

ingressimages=(nginx-ingress-controller:${INGRESS_VERSION})

for ingressimageName in ${ingressimages[@]} ; do
docker pull $UCLOUD_URL/$ingressimageName
docker tag $UCLOUD_URL/$ingressimageName $QUAY_URL/kubernetes-ingress-controller/$ingressimageName
docker rmi $UCLOUD_URL/$ingressimageName
done

csiimages=(csi-provisioner:${CSI_PROVISIONER_VERSION}
csi-node-driver-registrar:${CSI_NODE_DRIVER_VERSION}
csi-attacher:${CSI_ATTACHER_VERSION}
csi-resizer:${CSI_RESIZER_VERSION}
)

for csiimageName in ${csiimages[@]} ; do
docker pull $UCLOUD_URL/$csiimageName
docker tag $UCLOUD_URL/$csiimageName $QUAY_URL/k8scsi/$csiimageName
docker rmi $UCLOUD_URL/$csiimageName
done
[root@master01 ~]# for all_ip in ${ALL_IPS[@]}
  do
    echo ">>> ${all_ip}"
    scp -rp config/downimage.sh root@${all_ip}:/root/
    ssh root@${all_ip} "bash downimage.sh &"
  done

提示:如上僅需Master01節點操作,從而實現所有節點自動拉取鏡像。
[root@master01 ~]# docker images #確認驗證

Master上初始化

[root@master01 ~]# kubeadm init --config=kubeadm-config.yaml --upload-certs  

保留如下命令用於後續節點添加:
You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join 172.24.8.100:16443 --token xb9wda.v0yf7tlsgo8mdrhk \
    --discovery-token-ca-cert-hash sha256:249884d81a23bd821e38d3345866a99e6d55e443b545825c3c448f30f8e52c3b \
    --control-plane --certificate-key e30428776a47ed2c7e18c9e2951d9e40e068c9ecec5a4858457f1475f1a2a39a

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 172.24.8.100:16443 --token xb9wda.v0yf7tlsgo8mdrhk \
    --discovery-token-ca-cert-hash sha256:249884d81a23bd821e38d3345866a99e6d55e443b545825c3c448f30f8e52c3b

注意:如上token具有默認24小時的有效期,token和hash值可通過如下方式獲取: kubeadm token list 如果 Token 過期以後,可以輸入以下命令,生成新的 Token:

kubeadm token create
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'***

[root@master01 ~]# mkdir -p $HOME/.kube [root@master01 ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config [root@master01 ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config

[root@master01 ~]# cat << EOF >> ~/.bashrc
export KUBECONFIG=$HOME/.kube/config
EOF							#設置KUBECONFIG環境變量
[root@master01 ~]# echo "source <(kubectl completion bash)" >> ~/.bashrc
[root@master01 ~]# source ~/.bashrc

附加:初始化過程大致步驟如下:

  1. [kubelet-start] 生成kubelet的配置文件”/var/lib/kubelet/config.yaml”
  2. [certificates]生成相關的各種證書
  3. [kubeconfig]生成相關的kubeconfig文件
  4. [bootstraptoken]生成token記錄下來,後邊使用kubeadm join往集群中添加節點時會用到

提示:初始化僅需要在master01上執行,若初始化異常可通過kubeadm reset && rm -rf $HOME/.kube重置。

添加其他master節點

[root@master02 ~]# kubeadm join 172.24.8.100:16443 --token xb9wda.v0yf7tlsgo8mdrhk \
    --discovery-token-ca-cert-hash sha256:249884d81a23bd821e38d3345866a99e6d55e443b545825c3c448f30f8e52c3b \
    --control-plane --certificate-key e30428776a47ed2c7e18c9e2951d9e40e068c9ecec5a4858457f1475f1a2a39a
[root@master02 ~]# mkdir -p $HOME/.kube
[root@master02 ~]# sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
[root@master02 ~]# sudo chown $(id -u):$(id -g) $HOME/.kube/config
 [root@master02 ~]# cat << EOF >> ~/.bashrc`
export KUBECONFIG=$HOME/.kube/config
EOF						               	#設置KUBECONFIG環境變量
[root@master02 ~]# echo "source <(kubectl completion bash)" >> ~/.bashrc
[root@master02 ~]# source ~/.bashrc

提示:master03也如上執行添加至集群的controlplane。
提示:若添加異常可通過kubeadm reset && rm -rf $HOME/.kube重置。

安裝NIC插件

NIC插件介紹

  • Calico 是一個安全的 L3 網絡和網絡策略提供者。
  • Canal 結合 Flannel 和 Calico, 提供網絡和網絡策略。
  • Cilium 是一個 L3 網絡和網絡策略插件, 能夠透明的實施 HTTP/API/L7 策略。 同時支持路由(routing)和疊加/封裝( overlay/encapsulation)模式。
  • Contiv 為多種用例提供可配置網絡(使用 BGP 的原生 L3,使用 vxlan 的 overlay,經典 L2 和 Cisco-SDN/ACI)和豐富的策略框架。Contiv 項目完全開源。安裝工具同時提供基於和不基於 kubeadm 的安裝選項。
  • Flannel 是一個可以用於 Kubernetes 的 overlay 網絡提供者。 +Romana 是一個 pod 網絡的層 3 解決方案,並且支持 NetworkPolicy API。Kubeadm add-on 安裝細節可以在這裏找到。
  • Weave Net 提供了在網絡分組兩端參与工作的網絡和網絡策略,並且不需要額外的數據庫。
  • CNI-Genie 使 Kubernetes 無縫連接到一種 CNI 插件,例如:Flannel、Calico、Canal、Romana 或者 Weave。
    提示:本方案使用Calico插件。

設置標籤

[root@master01 ~]# kubectl taint nodes --all node-role.kubernetes.io/master- #允許master部署應用

提示:部署完內部應用后可使用kubectl taint node master01 node-role.kubernetes.io/master=””:NoSchedule重新設置Master為Master Only 狀態。

部署calico

[root@master01 ~]# cat config/calico/calico.yaml	#檢查配置
……
            - name: CALICO_IPV4POOL_CIDR
              value: "10.10.0.0/16"		        #檢查Pod網段
……
            - name: IP_AUTODETECTION_METHOD
              value: "interface=eth.*"		     	#檢查節點之間的網卡
# Auto-detect the BGP IP address.
            - name: IP
              value: "autodetect"
……
[root@master01 ~]# kubectl apply -f config/calico/calico.yaml
[root@master01 ~]# kubectl get pods --all-namespaces -o wide		#查看部署
[root@master01 ~]# kubectl get nodes

修改node端口範圍

[root@master01 ~]# vi /etc/kubernetes/manifests/kube-apiserver.yaml
……
    - --service-node-port-range=1-65535
……

部署高可用組件II

高可用說明

高可用kubernetes集群步驟三已完成配置,但是使用docker-compose方式啟動nginx-lb由於無法提供kubernetes集群的健康檢查和自動重啟功能,nginx-lb作為高可用kubernetes集群的核心組件建議也作為kubernetes集群中的一個pod來進行管理。

污點和標籤

[root@master01 ~]# kubectl taint node master01 node-`role.kubernetes.io/master="":NoSchedule
[root@master01 ~]# kubectl taint node master02 node-role.kubernetes.io/master="":NoSchedule
[root@master01 ~]# kubectl taint node master03 node-role.kubernetes.io/master="":NoSchedule
[root@master01 ~]# kubectl label nodes master01 node-role.kubernetes.io/master="true" --overwrite
[root@master01 ~]# kubectl label nodes master02 node-role.kubernetes.io/master="true" --overwrite
[root@master01 ~]# kubectl label nodes master02 node-role.kubernetes.io/master="true" --overwrite

容器化實現高可用

[root@master01 ~]# for master_ip in ${MASTER_IPS[@]}
  do
    echo ">>> ${master_ip}"
    ssh root@${master_ip} "systemctl stop kubelet"
    ssh root@${master_ip} "docker stop nginx-lb && docker rm nginx-lb"
    scp -rp /root/config/k8s-nginx-lb.yaml root@${master_ip}:/etc/kubernetes/manifests/
    ssh root@${master_ip} "systemctl restart kubelet docker"
  done

提示:如上僅需Master01節點操作,從而實現所有Master節點自動啟動服務。

[root@master01 ~]# kubectl -n kube-system get pods -o wide | grep -E 'NAME|nginx'

添加Worker節點

添加Worker節點

[root@master01 ~]# source environment.sh

[root@master01 ~]# for node_ip in ${NODE_IPS[@]}
  do
    echo ">>> ${node_ip}"
    ssh root@${node_ip} "kubeadm join 172.24.8.100:16443 --token xb9wda.v0yf7tlsgo8mdrhk \
    --discovery-token-ca-cert-hash sha256:249884d81a23bd821e38d3345866a99e6d55e443b545825c3c448f30f8e52c3b"
    ssh root@${node_ip} "systemctl enable kubelet.service"
  done

提示:如上僅需Master01節點操作,從而實現所有Worker節點添加至集群,若添加異常可通過如下方式重置:

[root@node01 ~]# kubeadm reset
[root@node01 ~]# ifconfig cni0 down
[root@node01 ~]# ip link delete cni0
[root@node01 ~]# ifconfig flannel.1 down
[root@node01 ~]# ip link delete flannel.1
[root@node01 ~]# rm -rf /var/lib/cni/

確認驗證

[root@master01 ~]# kubectl get nodes			         	#節點狀態
[root@master01 ~]# kubectl get cs			             	#組件狀態
[root@master01 ~]# kubectl get serviceaccount		     	        #服務賬戶
[root@master01 ~]# kubectl cluster-info			         	#集群信息
[root@master01 ~]# kubectl get pod -n kube-system -o wide	        #所有服務狀態

提示:更多Kubetcl使用參考:https://kubernetes.io/docs/reference/kubectl/kubectl/ https://kubernetes.io/docs/reference/kubectl/overview/
更多kubeadm使用參考:https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/

Metrics部署

Metrics

Kubernetes的早期版本依靠Heapster來實現完整的性能數據採集和監控功能,Kubernetes從1.8版本開始,性能數據開始以Metrics API的方式提供標準化接口,並且從1.10版本開始將Heapster替換為Metrics Server。在Kubernetes新的監控體系中,Metrics Server用於提供核心指標(Core Metrics),包括Node、Pod的CPU和內存使用指標。 對其他自定義指標(Custom Metrics)的監控則由Prometheus等組件來完成。

開啟聚合層

有關聚合層知識參考:https://blog.csdn.net/liukuan73/article/details/81352637 kubeadm方式部署默認已開啟。

獲取部署文件

[root@master01 ~]# mkdir metrics
[root@master01 ~]# cd metrics/
[root@master01 metrics]# wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.6/components.yaml
[root@master01 metrics]# vi components.yaml
……
apiVersion: apps/v1
kind: Deployment
……
spec:
  replicas: 3						        	#根據集群規模調整副本數
……
    spec:
      hostNetwork: true
……
      - name: metrics-server
        image: k8s.gcr.io/metrics-server-amd64:v0.3.6
        imagePullPolicy: IfNotPresent
        args:
          - --cert-dir=/tmp
          - --secure-port=4443
          - --kubelet-insecure-tls				        #追加此args
          - --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP	#追加此args
……

正式部署

[root@master01 metrics]# kubectl apply -f components.yaml
[root@master01 metrics]# kubectl -n kube-system get pods -l k8s-app=metrics-server
NAME                              READY   STATUS    RESTARTS   AGE
metrics-server-7b97647899-ghnxw   1/1     Running   0          11s
metrics-server-7b97647899-nqwvq   1/1     Running   0          10s
metrics-server-7b97647899-zkmxs   1/1     Running   0          10s

查看資源監控

[root@k8smaster01 ~]# kubectl top nodes
[root@k8smaster01 ~]# kubectl top pods --all-namespaces

提示:Metrics Server提供的數據也可以供HPA控制器使用,以實現基於CPU使用率或內存使用值的Pod自動擴縮容功能。
部署參考:https://linux48.com/container/2019-11-13-metrics-server.html 有關metrics更多部署參考: https://kubernetes.io/docs/tasks/debug-application-cluster/resource-metrics-pipeline/
開啟開啟API Aggregation參考: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/
API Aggregation介紹參考: https://kubernetes.io/docs/tasks/access-kubernetes-api/configure-aggregation-layer/

Nginx ingress部署

參考附020.Nginx-ingress部署及使用,建議採用社區版。

Dashboard部署

設置標籤

[root@master01 ~]# kubectl label nodes master01 dashboard=yes
[root@master01 ~]# kubectl label nodes master02 dashboard=yes
[root@master01 ~]# kubectl label nodes master03 dashboard=yes

創建證書

本實驗已獲取免費一年的證書,免費證書獲取可參考:https://freessl.cn。

[root@master01 ~]# mkdir -p /root/dashboard/certs
[root@master01 ~]# cd /root/dashboard/certs
[root@master01 certs]# mv k8s.odocker.com tls.crt
[root@master01 certs]# mv k8s.odocker.com tls.crt
[root@master01 certs]# ll
total 8.0K
-rw-r--r-- 1 root root 1.9K Jun  8 11:46 tls.crt
-rw-r--r-- 1 root root 1.7K Jun  8 11:46 tls.ke

提示:也可手動如下操作創建自簽證書:

[root@master01 ~]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=ZheJiang/L=HangZhou/O=Xianghy/OU=Xianghy/CN=k8s.odocker.com"

手動創建secret

[root@master01 ~]# kubectl create ns kubernetes-dashboard	#v2版本dashboard獨立ns
[root@master01 ~]# kubectl create secret generic kubernetes-dashboard-certs --from-file=$HOME/dashboard/certs/ -n kubernetes-dashboard
[root@master01 ~]# kubectl get secret kubernetes-dashboard-certs -n kubernetes-dashboard -o yaml		#查看新證書`

下載yaml

[root@master01 ~]# cd /root/dashboard
[root@master01 dashboard]# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.1/aio/deploy/recommended.yaml

修改yaml

[root@master01 dashboard]# vi recommended.yaml
……
kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  type: NodePort			       	#新增
  ports:
    - port: 443
      targetPort: 8443
      nodePort: 30001				#新增
  selector:
    k8s-app: kubernetes-dashboard
---
……						#如下全部註釋
#apiVersion: v1
#kind: Secret
#metadata:
#  labels:
#    k8s-app: kubernetes-dashboard
#  name: kubernetes-dashboard-certs
#  namespace: kubernetes-dashboard
#type: Opaque
……
kind: Deployment
……
  replicas: 3					        #適當調整為3副本
……
          imagePullPolicy: IfNotPresent		        #修改鏡像下載策略
          ports:
            - containerPort: 8443
              protocol: TCP
          args:
            - --auto-generate-certificates
            - --namespace=kubernetes-dashboard
            - --tls-key-file=tls.key
            - --tls-cert-file=tls.crt
            - --token-ttl=3600	       		        #追加如上args
……
      nodeSelector:
        "beta.kubernetes.io/os": linux
        "dashboard": "yes"	        		#部署在master節點
……
kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  type: NodePort	             			#新增
  ports:
    - port: 8000
      nodePort: 30000		         		#新增
      targetPort: 8000
  selector:                                                                                  
    k8s-app: dashboard-metrics-scraper
……
   replicas: 3			            		#適當調整為3副本
……
      nodeSelector:
        "beta.kubernetes.io/os": linux
        "dashboard": "yes"	        		#部署在master節點
……

正式部署

[root@master01 dashboard]# kubectl apply -f recommended.yaml
[root@master01 dashboard]# kubectl get deployment kubernetes-dashboard -n kubernetes-dashboard
[root@master01 dashboard]# kubectl get services -n kubernetes-dashboard
[root@master01 dashboard]# kubectl get pods -o wide -n kubernetes-dashboard

提示:master01 NodePort 30001/TCP映射到 dashboard pod 443 端口。

創建管理員賬戶

提示:dashboard v2版本默認沒有創建具有管理員權限的賬戶,可如下操作創建。

[root@master01 dashboard]# vi dashboard-admin.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard

[root@master01 dashboard]# kubectl apply -f dashboard-admin.yaml

ingress暴露dashboard

創建ingress tls

[root@master01 ~]# cd /root/dashboard/certs
[root@master01 certs]# kubectl -n kubernetes-dashboard create secret tls kubernetes-dashboard-tls --cert=tls.crt --key=tls.key
[root@master01 certs]# kubectl -n kubernetes-dashboard describe secrets kubernetes-dashboard-tls

創建ingress策略

[root@master01 ~]# cd /root/dashboard/
[root@master01 dashboard]# vi dashboard-ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: kubernetes-dashboard-ingress
  namespace: kubernetes-dashboard
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    #nginx.ingress.kubernetes.io/secure-backends: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_ssl_session_reuse off;
spec:
  rules:
  - host: k8s.odocker.com
    http:
      paths:
      - path: /
        backend:
          serviceName: kubernetes-dashboard
          servicePort: 443
  tls:
  - hosts:
    - k8s.odocker.com
    secretName: kubernetes-dashboard-tls
[root@master01 dashboard]# kubectl apply -f dashboard-ingress.yaml
[root@master01 dashboard]# kubectl -n kubernetes-dashboard get ingress

訪問dashboard

導入證書

將k8s.odocker.com導入瀏覽器,並設置為信任,導入操作略。

創建kubeconfig文件

使用token相對複雜,可將token添加至kubeconfig文件中,使用KubeConfig文件訪問dashboard。

[root@master01 dashboard]# ADMIN_SECRET=$(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}') 
[root@master01 dashboard]# DASHBOARD_LOGIN_TOKEN=$(kubectl describe secret -n kubernetes-dashboard ${ADMIN_SECRET} | grep -E '^token' | awk '{print $2}') 
[root@master01 dashboard]# kubectl config set-cluster kubernetes \
  --certificate-authority=/etc/kubernetes/pki/ca.crt \
  --embed-certs=true \
  --server=172.24.8.100:16443 \
  --kubeconfig=local-ngkek8s-dashboard-admin.kubeconfig		# 設置集群參數
 [root@master01 dashboard]# kubectl config set-credentials dashboard_user \
  --token=${DASHBOARD_LOGIN_TOKEN} \
  --kubeconfig=local-ngkek8s-dashboard-admin.kubeconfig		# 設置客戶端認證參數,使用上面創建的 Token
[root@master01 dashboard]# kubectl config set-context default \
  --cluster=kubernetes \
  --user=dashboard_user \
  --kubeconfig=local-ngkek8s-dashboard-admin.kubeconfig		# 設置上下文參數
[root@master01 dashboard]# kubectl config use-context default --kubeconfig=local-ngkek8s-dashboard-admin.kubeconfig		# 設置默認上下文

將local-ngkek8s-dashboard-admin.kubeconfig文件導入,以便於瀏覽器使用該文件登錄。

測試訪問dashboard

本實驗採用ingress所暴露的域名:https://k8s.odocker.com 方式訪問。使用local-ngkek8s-dashboard-admin.kubeconfig文件訪問。

提示: 更多dashboard訪問方式及認證可參考附004.Kubernetes Dashboard簡介及使用。 dashboard登錄整個流程可參考:https://www.cnadn.net/post/2613.html

Longhorn存儲部署

Longhorn概述

Longhorn是用於Kubernetes的開源分佈式塊存儲系統。
提示:更多介紹參考:https://github.com/longhorn/longhorn。

Longhorn部署

[root@master01 ~]# source environment.sh
[root@master01 ~]# for all_ip in ${ALL_IPS[@]}
  do
    echo ">>> ${all_ip}"
    ssh root@${all_ip} "yum -y install iscsi-initiator-utils &"
  done

提示:所有節點都需要安裝。

[root@master01 ~]# mkdir longhorn
[root@master01 ~]# cd longhorn/
[root@master01 longhorn]# wget \
https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
[root@master01 longhorn]# vi longhorn.yaml
#……
---
kind: Service
apiVersion: v1
metadata:
  labels:
    app: longhorn-ui
  name: longhorn-frontend
  namespace: longhorn-system
spec:
  type: NodePort			#修改為nodeport
  selector:
    app: longhorn-ui
  ports:
  - port: 80
    targetPort: 8000
    nodePort: 30002
---
……
kind: DaemonSet
……
        imagePullPolicy: IfNotPresent
……
#……
[root@master01 longhorn]# kubectl apply -f longhorn.yaml
[root@master01 longhorn]# kubectl -n longhorn-system get pods -o wide

提示:若部署異常可刪除重建,若出現無法刪除namespace,可通過如下操作進行刪除:

wget https://github.com/longhorn/longhorn/blob/master/uninstall/uninstall.yaml
rm -rf /var/lib/longhorn/
kubectl apply -f uninstall.yaml
kubectl delete -f longhorn.yaml

動態sc創建

提示:默認longhorn部署完成已創建一個sc,也可通過如下手動編寫yaml創建。

 [root@master01 longhorn]# kubectl get sc
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
……
longhorn               driver.longhorn.io      Delete          Immediate              true                   15m
[root@master01 longhorn]# vi longhornsc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: longhornsc
provisioner: rancher.io/longhorn
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "30"
  fromBackup: "" 

[root@master01 longhorn]# kubectl create -f longhornsc.yaml

測試PV及PVC

[root@master01 longhorn]# vi longhornpod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: longhorn-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: longhorn-pod
  namespace: default
spec:
  containers:
  - name: volume-test
    image: nginx:stable-alpine
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: volv
      mountPath: /data
    ports:
    - containerPort: 80
  volumes:
  - name: volv
    persistentVolumeClaim:
      claimName: longhorn-pvc
[root@master01 longhorn]# kubectl apply -f longhornpod.yaml
[root@master01 longhorn]# kubectl get pods
[root@master01 longhorn]# kubectl get pvc
[root@master01 longhorn]# kubectl get pv

創建ingress訪問UI

[root@master01 longhorn]# yum -y install httpd-tools
[root@master01 longhorn]# htpasswd -c auth xhy			#創建用戶名和密碼

提示:也可通過如下命令創建:
USER=xhy; PASSWORD=x120952576; echo "${USER}:$(openssl passwd -stdin -apr1 <<< ${PASSWORD})" >> auth

[root@master01 longhorn]# kubectl -n longhorn-system create secret generic longhorn-basic-auth --from-file=auth
[root@master01 longhorn]# vi longhorn-ingress.yaml		#創建ingress規則
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: longhorn-ingress
  namespace: longhorn-system
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: longhorn-basic-auth
    nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required '
spec:
  rules:
  - host: longhorn.odocker.com
    http:
      paths:
      - path: /
        backend:
          serviceName: longhorn-frontend
          servicePort: 80

[root@master01 longhorn]# kubectl apply -f longhorn-ingress.yaml

確認驗證

瀏覽器訪問:longhorn.odocker.com,並輸入賬號和密碼。

登錄查看。

Helm安裝

參考053.集群管理-Helm工具

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

【其他文章推薦】

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

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

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

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

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

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

SSH原理常見應用升級及端口轉發

SSH介紹

SSH是Secure Shell Protocol的簡寫,由IETF網絡工作小組(Network working Group)指定;在進行數據傳輸之前,SSH先對聯機數據包通過加密技術進行加密處理,加密后在進行數據傳輸。確保了傳遞的數據安全.

SSH是專為遠程登錄會話和其他網絡服務提供的安全性協議。利用SSH協議可以有效的放置遠程管理過程中的信息泄露問題,在當前的生產環境運維工作中,絕大多數企業普遍採用SSH協議服務來代替傳統的不安全的遠程聯機服務軟件,如telnet(23端口,非加密)

在默認狀態下,SSH服務主要提供了兩個服務功能,一個是提供類似Telnet遠程聯機服務器的服務,即上面提到的SSH服務;另一個是類似FTP服務的sftp-server,藉助SSH協議來傳輸數據的,提供更安全的SFTP服務(vsftp,proftp)

ssh 客戶端(ssh命令)還包含一個遠程安全拷貝命令scp,也是通過ssh協議工作.

小結

# 1、SSH是安全的加密協議,用於遠程連接linux服務器
# 2、SSH默認端口是22,安全協議版本SSH2,除了2之外還有SSH1(漏洞)
# 3、SSH服務端主要包含兩個服務協議SSH遠程連接,SFTP服務
# 4、Linux SSH客戶端包含ssh遠程連接命令,以及遠程拷貝scp命令

SSH結構

# SSH服務由服務端軟件OpenSSH (openssl)
# 客戶端(常見的有SSH(linux),SecureCRT,Putty,Xshell)組成

# SSH服務默認使用22端口提供服務,它有兩個不兼容的SSH協議版本分別是1.x和2.x

rpm -qa openssh
openssh-6.6.1p1-31.el7.x86_64		# 遠程連接安裝包

rpm -qa openssl
openssl-1.0.2k-19.el7.x86_64		# 加密安裝包

OpenSSH同時支持SSH 1.x和2.x 用SSH 2.x的客戶端程序不能鏈接到SSH1.x的服務程序上

SSH服務是一個守護進程(daemon),他在後台運行並響應來自客戶端的連接請求,SSH服務端的進程名為sshd,負責實時監聽遠程SSH客戶端的連接請求,並進行處理,一般包括公共密鑰認證、密鑰交換、對稱密鑰加密和非安全連接等。

SSH客戶端包含ssh以及像scp(遠程拷貝)slogin(遠程登錄)sftp(安全FTP文件傳輸)等應用程序

SSH的工作機制大致是本地的ssh客戶端發送一個連接請求到遠程的ssh服務器,服務器檢查連接的客戶端發送的數據包和ip地址,如果確認合法,就會發送密鑰給SSH的客戶端,此時,客戶端本地再將密鑰發回給服務端,自己建立連接。SSH1.x和SSH2.x在連接協議上有一些安全方面的差異

SSH加密技術

SSH加密技術是將人類可以看得懂的數據,通過一定的特殊的程序算法,把這些數據變成雜亂的無意義的信息,然後,通過網絡進行傳輸,二擋到了目的地后,在通過對應的解密算法,把傳過來的加密的數據信息解密成加密前的可讀取的正常數據。因此,當數據在互聯網上傳輸時即使被有心的黑客監聽竊取了,也很難獲取到真正需要的數據

網絡上的數據包加密技術一般是通過所謂的一對公鑰和私鑰(Public key and Pivate key)組合撐的密鑰對進行加密與解密操作。

SSH 1.x

每一台SSH服務器主機都可以使用RSA加密方式來產生一個1024-bit的RSA Key 這個RSA的加密方式就是用來產生公鑰和私鑰的算法之一。

當服務啟動時,就會產生一個768 bit的臨時公鑰存放在Server中

grep ServerKey /etc/ssh/sshd_config 
#ServerKeyBits 1024
SSH 2.x

在SSH 1.x的聯機過程中,當Server接收Clinet端的Private Key后,就不再針對該次聯機的Key pair進行檢驗。若此時有而已黑客對該聯機key pair插入而已的程序代碼時,由於服務端你不會在檢驗聯機的正確性,因此可能會接收該程序代碼,從而導致系統被黑.

為了改正這個缺點,SSH version2多加了一個確認聯機正確性的Diffie-Hellman機制

在每次數據傳輸中,Server都會以該機制檢查數據的來源是否正確,這樣,可以避免聯機過程中被插入而已程序代碼的問題

另外,SSH2同時支持RSA和DSA密鑰,但是SSH1僅支持RSA密鑰

由於SSH1協議本身存在較大問題,建議使用SSH2的聯機模式

當Client端SSH聯機請求傳送過來時,Server就會將這個768-bit的公鑰傳給Client端

此時Client會將此公鑰與先前存儲的公鑰進行對比,看是否一致,判斷標準是Client端聯機用戶目錄下~/.ssh/known_hosts文件的內容(linux-客戶端)

不加用戶默認是root 不加-p 指定端口 默認是22`

ssh -p22222 root@39.108.140.0
The authenticity of host '39.108.140.0 (39.108.140.0)' can't be established.
ECDSA key fingerprint is 17:33:ef:9b:05:b3:69:d3:20:48:49:e1:28:9b:7c:c8.
Are you sure you want to continue connecting (yes/no)? 

# 連接密碼文件存放路徑
cat /root/.ssh/known_hosts 
121.36.43.223 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDgQ7H6KDIPTzOklwMSOxgFI0Xc3rgvwPnCLIuXIuzaCfYQBouM6owCArpj2CXEyk40lSn96ktW1vETbP1JmjEY=

# 第一次SH連接的時候,本地會產生一個密鑰文件~/.ssh/known_hosts
如何防止SSH登錄入侵
# 1、用密鑰登錄,不用密碼登錄
# 2、牤牛陣法:解決SSH安全問題
# 3、防火牆封閉SSH,指定源限制(局域網,信任公網)
# 4、開啟SSH只監聽本地內網IP(ListenAddress 10.0.0.8)
# 5、盡量不給服務器外網IP

SSH客戶端附帶的scp命令

scp的基本語法使用:scp -sercure copy (remote file copy program)

推push

scp -P22 -rp /root/test.txt root@39.108.140.0:/root/

拉pull

scp -p22 -rp root@39.108.140.0:/root/test.txt ./

# -P 指定端口,默認22,可忽略
# -p 表示拷貝前後保持文件或目錄屬性
# -r 遞歸,表示拷貝目錄
# -l 限制速度

# 小結
# 1、scp是加密的遠程拷貝,而cp僅為本地拷貝
# 2、可以把數據從一台機器推送到另一台機器,也可以從其他服務器把數據拉回到本地執行命令的服務器
# 3、每次都是全量完成拷貝,因此效率不高,適合第一次拷貝用,如果需要增量拷貝,用rsync
ssh服務附帶的sftp功能服務
# 1. rz,sz(lrzsz)
# 2. winscp WinSCP-v4.0.5 基於SSH,sftp
# 3. SFX(xshell) 4) SFTP 基於SSH加密傳輸
# 4. samba,http,ftp,nfs
# FTP工具:vsftp、proftpd、SFTP

# linux sftp客戶端登錄sftp服務方法
# 登錄FTP的方法就是
sftp -oPort=22 root@39.108.140.0:
sftp> put test.txt
Uploading test.txt to /root/test.txt
test.txt                                          100%    0     0.0KB/s   00:00                                   
sftp> ls test.txt 
test.txt

SSH服務認證類型介紹

基於口令安全認證

基於口令的安全驗證的方式就是大家現在一直在用的,只要知道服務器的SSH端口號和口令,應服務器的IP及開放的端口,默認都為22,就可以通過ssh客戶端登錄到主機,此時聯機過程中所有傳輸都是加密的

基於密鑰的安全驗證

基於密鑰的安全驗證方式是指,需要依靠密鑰,也就是必須事先建立一對密鑰,然後把公用密鑰(Publickey)放在需要訪問的目標服務器上,另外,個還需要把私有密鑰(Private key)放到SSH客戶端或對應的客戶端服務器上

此時,如果要想連接到這個帶有公用密鑰的SSH服務器,客戶端SSH軟件或者客戶端端服務就會想SSH服務端發出請求,請求用聯機用戶密鑰進行安全連接。SSH服務會在收到請求之後,會現在改SSH服務器上連接的用戶的加密路下 放上去的對應用戶密鑰,然後把它和連接的SSH客戶端發來進行密鑰,如果兩個密鑰一直SSH服務就會用公用密鑰加密“質詢”(challenge)並把它發送給SSH客戶端

更改ssh 默認登錄配置

修改SSH服務的運行參數,是通過修改配置文件/etc/ssh/sshd_config實現的

一般來說SSH服務使用默認的配置已經夠很好的工作,如果對安全要求不高,僅僅提供SSH服務的情況不需要修改任何配置

sshd_config配置文件說明:

優化SSH配置文件選項
cp /etc/ssh/sshd_config{,.bak}
vim /etc/ssh/sshd_config
Port 52113              # ssh連接端口默認為22,修改端口號可以提高級別
PermitRootLogin no      # 禁止root遠程登錄
PermitEmptyPasswords no # 禁止空密碼的用戶登錄
UseDNS no               # 不使用DNS進行解析
GSSAPIAuthentication no # 會導致SSH連接慢

# ssh遠程連接服務滿解決方法
sed -ri '13 iPort 52113\nPermitRootLogin no\nPermitEmptyPasswords no\nUseDNS no\nGSSAPIAuthentication no' /etc/ssh/sshd_config

SSH優化

優化sshd_config
sed -i '13 iPort 52113\nPermitRootLogin no   \n   禁止root登錄
PermitEmptyPasswords no \n      #        禁止使用密碼
UseDNS no\n                     # 禁用DNS
GSSAPIAuthentication no'        #        禁用GSSAPI
sshd_config
檢查hosts解析
cat >>/etc/hosts <<EOF
39.108.140.0 blog
149.129.38.117 blog2
121.36.43.223 huawei 
49.233.69.195 tenxun 
116.196.83.113 jd 
EOF

useradd oldboy
echo "123456"|passwd --stdin youmen
su – youmen
ssh-keygen -t dsa   //非交互式創建密鑰  

#ssh-keygen是生產密鑰的工具 -t參數是指定密鑰的類型,這裡是建立dsa類型密鑰
#也可以使用ssh-keygen -t rsa來建立rsa類型密鑰

#RSA與DSA加密算法的區別
#RSA:是一種加密算法(PS:RSA也可以進行数字簽名的)它的簡寫的來由是RonRivest、Adi Shamir和LeonAdleman 這三個姓氏加在一起就是RSA

#DSA就是数字簽名算法的英文全稱的簡寫,即Digital Sigenature Algorithm=DSA

# RSA既可以進行加密,也可以進行数字簽名實現認證,而DSA只能用於数字簽名
分發密鑰
yum -y install sshpass
ssh-keygen -t rsa -P '' -f ~/.ssh/id_dsa &>/dev/null
sshpass -p youmen ssh -o StrictHostKeyChecking=no  root@192.168.43.159


# 一鍵生成密鑰對
ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa >/dev/null 2>&1

ssh-copy-id -i /root/.ssh/id_dsa.pub root@192.168.43.159
# -i 代表要發送的文件

# ssh-copy-id 只能發公鑰,不能發私鑰

# 1 免密碼登錄是單向的,方向從私鑰(鑰匙)==》公鑰(鎖)
# 2 SSH免密碼登錄基於用戶的,最好不要跨不同的用戶
# 3 ssh連接慢的問題解決
ssh批量管理步驟
# 1、ssh優化和hosts解析
# 2、創建用戶
# 3、生成密鑰對
# 4、分發公鑰到所有服務器 ssh-copy-id
# 5、測試 遠程連接ssh 遠程執行命令ssh ifconfig
# 遠程拷貝文件scp rsync-e隧道模式

ssh常見操作

將本地hosts發送指定IP

1. sudo提權實現沒有權限用戶拷貝

echo“youmenALL= NOPASSWD:/usr/bin/rsync ”>>/etc/sudoers

visudo -c 

scp -P52113 hosts oldboy@192.168.43.159:~	# 最好發送到家目錄下,直接發送到/下是沒有權限

ssh -p22 -t youmen@192.168.43.159 sudo rsync ~/hosts /etc/hosts
# 需要授權sudo權限

2. 使用suid實現沒有權限用戶拷貝

chmod u+s `which rsync`
scp -P22 hosts oldboy@192.168.43.159:~
ssh -p22 youmen@192.168.43.159 rsync ~/hosts  /etc/hosts

3. 使用root進行操作

# rsync使用
rsync -avz hosts -e ‘ssh -p 22’youmen@192.168.43.159:~
# 可以增量備份
查看hosts主機系統版本
 cat view.sh 
#!/bin/sh
for n in blog2 tenxun jd huawei 
do
  echo -n "====$n===="	
  ssh -p 22 $n $1		
done
[root@nginx_test ~]# sh view.sh "cat /etc/redhat-release"
====blog2====CentOS Linux release 7.3.1611 (Core) 
====tenxun====CentOS Linux release 7.6.1810 (Core) 
====jd====CentOS Linux release 7.6.1810 (Core) 
====huawei====CentOS Linux release 7.4.1708 (Core) 
分發文件
cat file.sh 
#!/bin/sh
. /etc/init.d/functions
if [ $# -ne 1 ];then 
    echo "USAGE:/bin/sh $0 FILENAME"	$
    exit 1    
fi
for n in blog2 tenxun jd huawei 
do
  echo -n "====$n===="	
  scp -P22 $1  $n: &>/dev/null
  if [ $? -eq 0 ];then
      action "dis $1 to $n" /bin/true
else
      action "$n" /bin/false
fi
done

sh file.sh /etc/hosts
====blog2====dis /etc/hosts to blog2                       [  OK  ]
====tenxun====dis /etc/hosts to tenxun                     [  OK  ]
====jd====dis /etc/hosts to jd                             [  OK  ]
====huawei====dis /etc/hosts to huawei                     [  OK  ]

SSH端口轉發簡介

SSH會自動加密和解密所有SSH客戶端與服務端之間的網絡數據。但是,SSH還能夠將其他TCP端口的網絡數據通SSH鏈接來轉發,並且自動提供了相應的加密及解密服務。這一過程也被叫做”隧道“(tunneling),這是因為SSH為其他TCP鏈接提供了一個安全的通道來進行傳輸而得名。例如,Telnet ,SMTP ,LDAP這些TCP應用均能夠從中得益,避免了用戶名,密碼以及隱私信息的明文傳輸。而與此同時,如果工作環境許中的防火牆限制了一些網絡端口的使用,但是允許SSH的連接,也能夠將通過將TCP用端口轉發來使用SSH進行通訊。

SSH端口轉發兩大功能
# 1. 加密SSH Client端至SSH Server端之間的通訊數據。
# 2. 突破防火牆的簡直完成一些之前無法建立的TCP連接。

本地轉發

命令 -L localport:remotehost:remotehostport sshserver

說明

localport      #  本機開啟的端口號
remotehost      # 最終連接機器的IP地址
remotehostport     #  轉發機器的端口號
sshserver      #  轉發機器的IP地址

# -L 本機端口
# -f 後台啟用,可以在本機直接執行命令,無需另開新終端
# -N 不打開遠程shell,處於等待狀態,不跳到遠程主機,還在主機上,只是搭好了隧道,橋搭好,不ssh上去
# -g 啟用網關功能
# -R 服務端口

# 舉例:
ssh –L 9527:telnetsrv:23 -N sshsrv
telnet 127.0.0.1 9527
# 當訪問本機的9527的端口時,被加密後轉發到sshsrv的ssh服務,再解密被轉發到telnetsrv:23
data < >localhost:9527 < > localhost:XXXXX < > sshsrv:22 < > sshrv:yyyyy < > telnetsrv:23
環境背景

背景:企業內部C服務器只允許telnet連接(23端口)訪問,不允許外部直接訪問,B服務器是一個ssh服務器;有一個用戶需要從外部連接到企業內部的C服務器。

前提:防火牆允許22端口進來(或者企業內部有一個堡壘機,ssh -t通過堡壘機進去)。

原理: 數據一旦telnet以後,數據會發送到本機9527端口,再在本機開一個隨機端口,充當ssh客戶端,再把數據流量發送到22端口的ssh服務端,收到數據以後,解密數據,臨時開一個隨機端口充當客戶端,再把流量發送到23端口telnet服務端

機器: blogA用戶,huawei模擬B機器,tenxun模擬C機器

節點名 IP 軟件版本 硬件 網絡 說明
CentOS7-A 39.108.140.0 1C2G 公有雲 阿里雲
CentOS6-B 121.36.43.223 1C2G 公有雲 華為雲
CentOS6-C 49.233.69.195 1C2G 公有雲 騰訊雲
# ssh協議裏面封裝了telnet,一旦A連接了B主機,立即使用telnet連接C主機,此過程可以突破防火牆的限制
# 實驗
#   A->C    訪問被限制
#    A-B->C  使用B主機作為跳板突破訪問限制
配置步驟

C機器通過iptables拒絕A機器登錄

[root@C ~]# iptables -A INPUT -s 39.108.140.0 -j REJECT
[root@C ~]# yum -y install telnet-server xinetd
[root@C ~]# systemctl start telnet.socket
[root@C ~]# systemctl start xinetd
[root@C ~]# ss -tnl |grep 23
LISTEN     0      128       [::]:23                    [::]:*   

# 此時我們A機器是直接連接不上C機器的
[root@A ~]# ssh 49.233.69.195
ssh: connect to host 49.233.69.195 port 22: Connection refused

# 開啟端口轉發(telnet隧道)
[root@A ~]# ssh -L 10000:49.233.69.195:23 -Nf 121.36.43.223
# 通過本地9527端口訪問centos6-1服務器IP地址使用telnet協議23端口,跳板機ip地址

# 隧道已經搭建好了,此時A主機可以通過telnet訪問C主機
telnet 127.0.0.1 10000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

Kernel 3.10.0-1062.9.1.el7.x86_64 on an x86_64
c login: youmen
Password: 
Last login: Fri Jun 12 22:32:04 from centos-b
[root@C ~]$

# 如何需要刪除這個連接使用killall ssh即可
killall ssh   # 刪除搭建的橋
telnet 127.0.0.1 10000
Trying 127.0.0.1...
telnet: connect to address 127.0.0.1: Connection refused

遠程轉發

遠程轉發機制

# -R sshserverport:remotehost:remotehostportsshserver

# 舉例
ssh–R 9527:telnetsrv:23 –N sshsrv
# 讓sshsrv偵聽9527端口的訪問,如有訪問,就加密后通過ssh服務轉發請求到本機ssh客戶端,再由本機解密後轉發到telnetsrv:23
Data < > sshsrv:9527 < > sshsrv:22 < > localhost:XXXXX < > localhost:YYYYY< >telnetsrv:23

# 需求:
# 在A(Centos7)上開啟smtp服務(postfix),B(Centos6)做跳板,C(Centos6-1)客戶端給Centos7發送郵件

# 流程解釋
C-x->A    (拒絕訪問)
C-B->A      (通過遠程代理,接受訪問)  

環境依然是上面三台機器,只是C服務器換成116.196.83.113 (JD)這台服務器

配置步驟
[root@A ~]# vim /etc/postfix/main.cf 
# inet_interfaces = localhost  # 註釋此行,不讓接口直接綁定在127.0.0.1上
[root@A ~]# systemctl restart postfix
[root@A ~]# ss -tnl |grep 25
LISTEN     0      100          *:25                       *:*                  
LISTEN     0      100         :::25                      :::*  

[root@A ~]# yum -y install telnet-server
[root@A ~]# systemctl start telnet.socket

# 如果B上面有一些影響的防火牆規則就將它刪掉
[root@B ~]# iptables -nL --line-number
[root@B ~]# iptables -D INPUT 1

# 測試下C能不能telnet 25端口連接A
[root@C ~]# 39.108.140.0 25
Trying 39.108.140.0...
Connected to 39.108.140.0.
Escape character is '^]'.
220 nginx_test.localdomain ESMTP Postfix

# 設置防火牆策略,使A不接受C的一切請求
[root@A ~]# iptables -A INPUT -s 39.108.140.0 -j REJECT
[root@C ~]# telnet 39.108.140.0 25
Trying 39.108.140.0...
telnet: connect to address 39.108.140.0: Connection refused

# 使用B遠程轉發,發送郵件到
[root@B ~]# ssh-copy-id root@116.196.83.113:
# 使用B遠程轉發,發送郵件到A,最好免密,否則需要手動口令驗證
[root@B ~]#  ssh -R 50000:39.108.140.0:25 -fN 121.36.43.223

# 此時可以到C跳板機看到已經有端口在監聽了
[root@C ~]# ss -tnl |grep 50000
LISTEN     0      128    127.0.0.1:50000                    *:*                

[root@C ~]# telnet 127.0.0.1 5000
Trying 127.0.0.1...
telnet: connect to address 127.0.0.1: Connection refused
[root@zabbix ~]# telnet 127.0.0.1 50000
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
220 nginx_test.localdomain ESMTP Postfix
mail from:youmen@163.com      
250 2.1.0 Ok

openssh升級

下載地址

http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/

具體升級步驟
# 記錄sshd.pid路徑
find / -name sshd.pid

# 查看openssh現有版本
ssh -V

# 使用rpm刪除現有的openssh
rpm -e --nodeps $(rpm -qa | grep openssh)

# 刪除舊的配置文件
rm -rf /etc/ssh/*

# 安裝openssl-devel
yum -y install openssl-devel

# 下載tar.gz包,配置編譯,安裝
wget http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-8.0p1.tar.gz

# 編譯並安裝
tar xvf openssh-8.0p1.tar.gz 
cd openssh-8.0p1
./configure --prefix=/usr/ --sysconfdir=/etc/ssh/ --with-ssl-dir=/etc/ssl --with-md5-passwords --mandir=/usr/share/man/
make && make install

# 設置ssh服務開機自啟動
# 複製啟動文件至/etc/init.d/
cp -a contrib/redhat/sshd.init /etc/init.d/sshd
     
# 編輯/etc/init.d/sshd文件, 將PID_FILE改為之前記下的sshd.pid路徑
    sed -i "s/PID_FILE=\/var\/run\/sshd.pid/PID_FILE=\/run\/sshd.pid/" /etc/init.d/sshd
     
# 設置開機啟動sshd
    chkconfig sshd on
    chkconfig --list sshd
    
    
# 編輯/etc/ssh/sshd_config, 設置一些常用參數
sed -i "s/#PermitRootLogin prohibit-password/PermitRootLogin yes/" /etc/ssh/sshd_config
sed -i "s/#PubkeyAuthentication yes/PubkeyAuthentication yes/" /etc/ssh/sshd_config
sed -i "s/#PasswordAuthentication yes/PasswordAuthentication yes/" /etc/ssh/sshd_config
sed -i "s/#AllowTcpForwarding yes/AllowTcpForwarding yes/" /etc/ssh/sshd_config
sed -i "s/#X11Forwarding no/X11Forwarding yes/" /etc/ssh/sshd_config
sed -i "s/#PidFile \/var\/run\/sshd.pid/PidFile \/run\/sshd.pid/" /etc/ssh/sshd_config

# 加入系統服務
cat > /usr/lib/systemd/system/sshd.service  << EOF
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
#After=network.target sshd-keygen.service
#Wants=sshd-keygen.service
After=network.target
[Service]
ExecStart=/usr/sbin/sshd
[Install]
WantedBy=multi-user.target
EOF

# 啟用sshd服務
systemctl enable sshd
# 重啟服務
systemctl restart sshd
# 查看服務狀態
systemctl status sshd

# 驗證
ssh -V
OpenSSH_8.0p1, OpenSSL 1.0.2k-fips  26 Jan 2017

SSH設置登錄歡迎信息

到/etc/motd裏面編寫內容,看個人愛好

[root@nginx_test ~]# cat /etc/motd 
  /\/\
  (_人|人_)
   /‥\
  ミ(_Y_)ミ
   > <
  (/ \)
  _(   )_
 (_>―<_)

# 關閉當前會話再登錄
  /\/\
  (_人|人_)
   /‥\
  ミ(_Y_)ミ
   > <
  (/ \)
  _(   )_
 (_>―<_)
[root@nginx_test ~]#
小熊圖案
┴┬┴┬/ ̄\_/ ̄\   
┬┴┬┴▏  ▏▔▔▔▔\   
┴┬┴/\ /      ﹨   
┬┴∕       /   )   
┴┬▏        ●  ▏   
┬┴▏           ▔█    
┴◢██◣     \___/   
┬█████◣       /     
┴█████████████◣   
◢██████████████▆▄   
█◤◢██◣◥█████████◤\   
◥◢████ ████████◤   \   
┴█████ ██████◤      ﹨   
┬│   │█████◤        ▏   
┴│   │              ▏   
┬ ∕    ∕    /▔▔▔\     ∕   
┴/___/﹨   ∕     ﹨  /\   
┬┴┬┴┬┴\    \      ﹨/   ﹨   
┴┬┴┬┴┬┴ \___\     ﹨/▔\﹨ ▔\   
▲△▲▲╓╥╥╥╥╥╥╥╥\   ∕  /▔﹨/▔﹨   
   **╠╬╬╬╬╬╬╬╬*﹨  /  //

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

【其他文章推薦】

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

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

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

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

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