VueRouter爬坑第一篇-簡單實踐

VueRouter系列的文章示例編寫時,項目是使用vue-cli腳手架搭建。

項目搭建的步驟和項目目錄專門寫了一篇文章:

後續VueRouter系列的文章的示例編寫均基於該項目環境。

 VueRouter系列文章鏈接

  

  《VueRouter爬坑第二篇》-動態路由

  《VueRouter爬坑第三篇》-嵌套路由

 本篇文章完整代碼請移步:

  

 

 

閱讀目錄

一.前言

二.安裝VueRouter

  1.npm 安裝VueRouter

  2.如何使用VueRouter

三.組件和路由之間的映射

  1.<router-link>編寫可以跳轉的url

  2.編寫跳轉的目的組件

  3.定義路由

  4.入口文件main.js配置路由

  5.配置組件渲染的位置

四.總結

 

一.前言

  關於項目的搭建我們在文章最前面已經給了鏈接,在項目搭建這篇文章的最底部有一個注意事項,為了能清楚的理解本篇文章,我再把注意事項貼在這裏:

  

  我們的項目在使用vue-cli初始化的時候是沒有安裝vue-router的,所以如果你的在初始化的時候安裝了vue-router,那這篇文章的有些部分可能不太適用了。

  請大家謹慎觀看。

二.安裝VueRouter

1.npm 安裝VueRouter

  安裝命令:npm install vue-router

  

2.如何使用VueRouter

  安裝完成之後,需要有兩個步驟才能使用

  第一步:引入vue-router

    

  第二步:將組件註冊到全局(全局註冊后,在別的組件中可以直接使用,無需單獨引入)

     

   這塊先貼出步驟,暫時不寫代碼,後面做組件和路由映射的時候在把代碼寫上。

三.組件和路由之間的映射

  接着我們的疑問就來了:頁面中的一個URL如何匹配到我們編寫的組件?下面我們就一步一步揭開這個疑惑。

1.<router-link>編寫可以跳轉的url

   關於router-link的官方文檔說明可查看

  關於上面的疑問,我們第一步需要先編寫一個可以跳轉的URL。我們先把項目中默認生成的HelloWorld.vue組件中div.hello中的內容刪除,然後在加上我們自己的內容。

 

E:\MyStudy\test\VueDemo\src\components\HelloWorld.vue

 

<template>
  <div class="hello">
    <p>這裡是HelloWorld組件</p>
    <!-- 可跳轉的URL -->
    <router-link to="/index">點擊這裏渲染出另外一個組件</router-link>   
  </div>
</template>
// 後面的script和style沒有修改,因此省略

 

App組件不做修改,這裏就不貼代碼了,接着我們使用npm run dev啟動項目查看一下瀏覽器效果。

web頁面效果

 

<template> <div class=”hello”> <p>這裡是HelloWorld組件</p> <!– 可跳轉的URL –> <router-link to=”/index”>點擊這裏去另外一個組件</router-link> </div> </template> // 後面的script和style沒有修改,因此省

2.編寫跳轉的目的組件

  因為我們要實現url跳轉,因此需要編寫一個跳轉的目的組件。在這裏我們創建一個組件Index。

E:\MyStudy\test\VueDemo\src\components\Index.vue

<template>
    <div>  
        <h1>這裡是Index.vue組件</h1>
    </div>
</template>
<script>
export default {
    name: 'Index'
}
</script>

3.定義路由

  現在可點擊的URL準備好了,跳轉的目標組件也準備好了。接下來就是定義路由配置:將URL映射到組件。

  一般項目中為了後續好維護,會將路由配置單獨寫在一個文件中。因此我們先在E:\MyStudy\test\VueDemo\src 目錄下創建一個rotuer目錄,在該目錄下創建一個router.js文件,後面所有的路由配置均在該文件中編寫。

  創建路由配置文件

  

E:\MyStudy\test\VueDemo\src\router\router.js

// vue-router使用第一步:引入vue-router
import Vue from "vue"
import Router from "vue-router"

// vue-router使用第二步:組件註冊到全局
Vue.use(Router)

// 第三步:定義路由配置
// 引入路由需要映射的組件
import Index from '@/components/Index.vue'
const routes = [
    {
        path: '/index',   // 匹配的URL
        component: Index  //映射的目標組件
    }
]

// 第四步:創建vue-router實例,傳遞前面定義的路由配置
const router = new Router({
    routes: routes
})

// 第五步:將vue-router實例使用export導出(供其他模塊使用)
export default router 

  步驟一和步驟二在前面我們已經說過,步驟四和步驟五基本都是固定的配置不可缺少。

  第三個步驟中的routes就是稱為路由配置,可以看到routes是一個數組,它可以包含多個字典對象。每一個字典對象就是一條單個的路由。

  我們寫的這個路由只有最簡單的兩個配置項:path和component,註釋中有說明這兩個配置項的含義。

 

4.入口文件main.js配置路由

  前面的三個部分完成后,還需要在入口文件處把這個路由實例注入到跟組件中,這樣整個應用都可以擁有路由功能。

E:\MyStudy\test\VueDemo\src\main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router/router'
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>',
  //將路由實例註冊到根組件中
  router: router
})

 

大功告成,我們現在點擊界面的鏈接試一下

 

 

 

 

   在這裏呢,賣了個關子少了一個步驟:沒有告訴vue-router把匹配到的組件渲染到哪裡。

5.配置組件渲染的位置

<!-- 使用下面的這個標籤可以告訴vue-router將匹配到的組件渲染到這個位置 -->
<router-view>  </router-view>

 

然後我們將該段代碼加入到HelloWorld.vue這個組件中。

E:\MyStudy\test\VueDemo\src\components\HelloWorld.vue

<template>
  <div class="hello">
    <p>這裡是HelloWorld組件</p>
    <!-- 可跳轉的URL -->
    <router-link to="/index">點擊這裏去另外一個組件</router-link>
    
    <!-- 使用下面的這個標籤可以告訴vue-router將匹配到的組件渲染到這個位置 -->
    <router-view></router-view>
  </div>
</template>
// 後面的script和style沒有修改,因此省略

 

現在我們重新試一下效果

 

 

 

 

 

現在可以看到點擊鏈接后界面發生了變化:

index.vue中的內容显示到了HelloWorld.vue中router-view配置的位置

url由localhost:8080/#/變為localhost:8080/#/index

四.總結

  至此,一個簡單的url路由到組件的實例就完成了,現在總結一下前面梳理過的內容

  1.需要使用npm安裝vue-router。

注意:在使用veue init 初始化項目的時候會提示是否安裝vue-router,如果選擇是,那後續就不需要再次手動安裝了。

  2.可以使用<router-link/>編寫可跳轉的url,並使用<router-view/>指定組件的渲染位置

  3.創建Vue-Router實例,傳入配置的路由:最簡單的路由配置就是path和component

 

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

[ PyQt入門教程 ] PyQt5中數據表格控件QTableWidget使用方法

 如果你想讓你開發的PyQt5工具展示的數據顯得整齊、美觀、好看,顯得符合你的氣質,可以考慮使用QTableWidget控件。之前一直使用的是textBrowser文本框控件,數據展示還是不太美觀。其中QTableWidget是PyQt5程序中常用的显示數據表格的控件,显示的基本效果如下,有點素。。

  下面開始介紹QTableWidget常用的方法以及如何使用。既然是數據表格形式,經常使用的場景其實跟excel我覺得差不多。開始學習使用QTableWidget之前,我們帶着如下幾個問題再開始本文的閱讀。

  1、如何在GUI界面上創建QTableWidget表格?指定N行M列,列名稱等等

  2、如何在表格里添加每一個單元格的數據?

  3、如何排版數據表格的數據,比如你想單元格內容居中、左對齊、右對齊等。

  4、如何設置文字显示顏色、如何設置單元格背景色?

  5、如何合併指定單元格,讓排版更合理?

  基本上使用數據表格展示數據常見的問題就是這些,能夠熟練使用QTableWidget解決上述問題,QTableWidget基本使用方法應該就會了。下面開始學習本文內容。

QTableWidget常用方法

  setROwCount(int row)     設置QTableWidget表格控件的行數

  setColumnCount(int col)     設置QTableWidget表格控件的列數

  setHorizontalHeaderLabels()     設置QTableWidget表格控件的水平標籤

  setVerticalHeaderLabels()     設置QTableWidget表格控件的垂直標籤

  setItem(int ,int ,QTableWidgetItem)     在QTableWidget表格控件的每個選項的單元控件內添加控件

  horizontalHeader()     獲得QTableWidget表格控件的表格頭,以便執行隱藏

  rowCount()     獲得QTableWidget表格控件的行數

  columnCount()     獲得QTableWidget表格控件的列數

  setEditTriggers(EditTriggers triggers)     設置表格是否可以編輯,設置表格的枚舉值

  setSelectionBehavior     設置表格的選擇行為

  setTextAlignment()     設置單元格內文本的對齊方式

  setSpan(int row,int column,int rowSpanCount,int columnSpanCount)     合併單元格,要改變單元格的第row行,column列,要合併rowSpancount行數和columnSpanCount列數。其中row表示要改變的行數, column表示要改變的列數,rowSpanCount表示需要合併的行數,columnSpanCount表示需要合併的列數。

  setShowGrid()     在默認情況下錶格的显示是有網格的,可以設置True或False用於是否显示,默認True

  setColumnWidth(int column,int width)     設置單元格行的寬度

  setRowHeight(int row,int height)     設置單元格列的高度

  這裏的函數有些有點不好理解,比如setSpan()合併單元格函數,你可能一下不知道它的使用方法以及實現效果。沒關係,下面會通過實例講解以及效果演示展示這些函數的使用方法。

QTableWidget使用實例

 1、使用designer實現一個包含QTableWidget數據展示控件的窗體。界面設計一般都會採用designer工具,因為要考慮控件間的布局,純代碼實現會增加難度。界面實現如下

 雙擊在窗體界面上的QTableWidget控件,分別選擇Edit Table Widget中Columns、Rows、Items進行編輯。可以分別完成行、列標題以及單元格內容的添加。

 完成后,效果圖如下

 2、使用pyuic5工具將.ui文件轉換為.py文件。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'cc.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_widget(object):
    def setupUi(self, widget):
        widget.setObjectName("widget")
        widget.resize(730, 574)
        self.tableWidget = QtWidgets.QTableWidget(widget)
        self.tableWidget.setGeometry(QtCore.QRect(10, 130, 701, 192))
        self.tableWidget.setObjectName("tableWidget")
        self.tableWidget.setColumnCount(4)
        self.tableWidget.setRowCount(3)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setVerticalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setVerticalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setVerticalHeaderItem(2, item)
        item = QtWidgets.QTableWidgetItem()
        item.setTextAlignment(QtCore.Qt.AlignCenter)
        self.tableWidget.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setHorizontalHeaderItem(3, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(0, 3, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(1, 3, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 0, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 1, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 2, item)
        item = QtWidgets.QTableWidgetItem()
        self.tableWidget.setItem(2, 3, item)
        self.tableWidget.horizontalHeader().setCascadingSectionResizes(True)

        self.retranslateUi(widget)
        QtCore.QMetaObject.connectSlotsByName(widget)

    def retranslateUi(self, widget):
        _translate = QtCore.QCoreApplication.translate
        widget.setWindowTitle(_translate("widget", "員工信息表"))
        item = self.tableWidget.verticalHeaderItem(0)
        item.setText(_translate("widget", "1"))
        item = self.tableWidget.verticalHeaderItem(1)
        item.setText(_translate("widget", "2"))
        item = self.tableWidget.verticalHeaderItem(2)
        item.setText(_translate("widget", "3"))
        item = self.tableWidget.horizontalHeaderItem(0)
        item.setText(_translate("widget", "姓名"))
        item = self.tableWidget.horizontalHeaderItem(1)
        item.setText(_translate("widget", "年齡"))
        item = self.tableWidget.horizontalHeaderItem(2)
        item.setText(_translate("widget", "性別"))
        item = self.tableWidget.horizontalHeaderItem(3)
        item.setText(_translate("widget", "基本信息"))
        __sortingEnabled = self.tableWidget.isSortingEnabled()
        self.tableWidget.setSortingEnabled(False)
        item = self.tableWidget.item(0, 0)
        item.setText(_translate("widget", "張三"))
        item = self.tableWidget.item(0, 1)
        item.setText(_translate("widget", "23"))
        item = self.tableWidget.item(0, 2)
        item.setText(_translate("widget", "Male"))
        item = self.tableWidget.item(0, 3)
        item.setText(_translate("widget", "家裡有礦"))
        item = self.tableWidget.item(1, 0)
        item.setText(_translate("widget", "王八"))
        item = self.tableWidget.item(1, 1)
        item.setText(_translate("widget", "25"))
        item = self.tableWidget.item(1, 2)
        item.setText(_translate("widget", "Female"))
        item = self.tableWidget.item(1, 3)
        item.setText(_translate("widget", "拆二代"))
        item = self.tableWidget.item(2, 0)
        item.setText(_translate("widget", "趙四"))
        item = self.tableWidget.item(2, 1)
        item.setText(_translate("widget", "28"))
        item = self.tableWidget.item(2, 2)
        item.setText(_translate("widget", "Male"))
        item = self.tableWidget.item(2, 3)
        item.setText(_translate("widget", "勤奮努力的程序員"))
        self.tableWidget.setSortingEnabled(__sortingEnabled)

附designer工具設計完成的.ui文件代碼。不作展示,方便有需要的讀者下載使用。

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>widget</class>
 <widget class="QWidget" name="widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>730</width>
    <height>286</height>
   </rect>
  </property>
  <property name="font">
   <font>
    <pointsize>10</pointsize>
   </font>
  </property>
  <property name="windowTitle">
   <string>員工信息表</string>
  </property>
  <widget class="QTableWidget" name="tableWidget">
   <property name="geometry">
    <rect>
     <x>10</x>
     <y>51</y>
     <width>701</width>
     <height>211</height>
    </rect>
   </property>
   <attribute name="horizontalHeaderCascadingSectionResizes">
    <bool>true</bool>
   </attribute>
   <row>
    <property name="text">
     <string>1</string>
    </property>
   </row>
   <row>
    <property name="text">
     <string>2</string>
    </property>
   </row>
   <row>
    <property name="text">
     <string>3</string>
    </property>
   </row>
   <column>
    <property name="text">
     <string>姓名</string>
    </property>
    <property name="textAlignment">
     <set>AlignCenter</set>
    </property>
   </column>
   <column>
    <property name="text">
     <string>年齡</string>
    </property>
   </column>
   <column>
    <property name="text">
     <string>性別</string>
    </property>
   </column>
   <column>
    <property name="text">
     <string>基本信息</string>
    </property>
   </column>
   <item row="0" column="0">
    <property name="text">
     <string>張三</string>
    </property>
   </item>
   <item row="0" column="1">
    <property name="text">
     <string>23</string>
    </property>
   </item>
   <item row="0" column="2">
    <property name="text">
     <string>Male</string>
    </property>
   </item>
   <item row="0" column="3">
    <property name="text">
     <string>家裡有礦</string>
    </property>
   </item>
   <item row="1" column="0">
    <property name="text">
     <string>王八</string>
    </property>
   </item>
   <item row="1" column="1">
    <property name="text">
     <string>25</string>
    </property>
   </item>
   <item row="1" column="2">
    <property name="text">
     <string>Female</string>
    </property>
   </item>
   <item row="1" column="3">
    <property name="text">
     <string>拆二代</string>
    </property>
   </item>
   <item row="2" column="0">
    <property name="text">
     <string>趙四</string>
    </property>
   </item>
   <item row="2" column="1">
    <property name="text">
     <string>28</string>
    </property>
   </item>
   <item row="2" column="2">
    <property name="text">
     <string>Male</string>
    </property>
   </item>
   <item row="2" column="3">
    <property name="text">
     <string>勤奮努力的程序員</string>
    </property>
   </item>
  </widget>
  <widget class="QLabel" name="label">
   <property name="geometry">
    <rect>
     <x>250</x>
     <y>20</y>
     <width>111</width>
     <height>21</height>
    </rect>
   </property>
   <property name="font">
    <font>
     <family>YaHei Consolas Hybrid</family>
     <pointsize>10</pointsize>
    </font>
   </property>
   <property name="text">
    <string>員工信息展示</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

View Code

 3、編寫主程序,調用界面.py文件。使業界面和邏輯程序分離,這樣的好處就是後面界面任何改動程序邏輯幾乎不會有什麼大的影響。

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'connect_me.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
#導入程序運行必須模塊
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QColor, QBrush
from cc import Ui_widget


class MyMainForm(QMainWindow, Ui_widget):
    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWin = MyMainForm()
    myWin.show()
    sys.exit(app.exec_())

 運行程序結果如下:

 4、程序運行可以正常显示界面,但是界面並不是很美觀(比如行、列沒有鋪滿整個显示窗口、列標題文字沒有加粗等)。下面通過QTableWidget相關函數的使用來實現優化。

QTableWidget函數使用方法

  上述基本例子實現了員工信息表格展示的初始化,單元格內容手工添加其實在實現場景不合適,只是方便展示,下面講解如何通過代碼實現方式添加單元格內容以及實現單元格內容顏色、字體變化的實現。說明,下述所有代碼添加都在主程序中完成。目的是使程序實現更靈活,方便維護。

 (1)初始化QTableWidget數據展示窗口對象。一般來說該步驟通常是通過designer實現。

self.tableWidget = QtWidgets.QTableWidget(widget) self.tableWidget.setColumnCount(4) self.tableWidget.setRowCount(3)

  設置表格的行、列標題,如下:

self.tableWidget.setHorizontalHeaderLabels(['姓名','年齡']) self.tableWidget.setVerticalHeaderLabels(['1','2'])

 當然也可以通過上面例子中的方式去添加,如下:

item = QtWidgets.QTableWidgetItem()
self.tableWidget.setVerticalHeaderItem(1, item)

item = QtWidgets.QTableWidgetItem()
self.tableWidget.setHorizontalHeaderItem(1, item)

(2)設置表格頭的伸縮模式,也就是讓表格鋪滿整個QTableWidget控件。

self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
效果如下:

 (2)將表格變為禁止編輯。默認情況下錶格中的字符串是可以更改的,比如雙擊一個單元格,就可以修改運來的內容。如果想禁止這種操作,讓表格對用戶是只讀的,可以添加如下代碼。

self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)

運行效果(雙擊單元格不可編輯,提前觀察一下鼠標單擊選中的只是單元格):

(3)設置表格整行選中。表格默認選中的是單個單元格。通過下面代碼可以設置成選中整行。添加代碼如下

self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows)

運行效果如下:

  另外,單元格選中的類型還可以修改成如下方式:

  QAbstractItemView.SelectColumns 選中1列

  QAbstractItemView.SelectRows  選中1行

  QAbstractItemView.SelectItems 選中1個單元格

(4)行、列標題的显示與隱藏。

  對於列標題的显示或隱藏設置,可以通過添加如下代碼實現。默認是显示的。

self.tableWidget.horizontalHeader().setVisible(False)

運行效果如下(行標題不再显示):

對於行標題,可以使用如下代碼進行隱藏或显示設置。默認是显示
self.tableWidget.verticalHeader().setVisible(

 (5)在表格中添加內容。代碼如下:

        items = [['燕十三','21','Male','武林大俠'],['蕭十一郎','21','Male','武功好']]

        for i in range(len(items)):
            item = items[i]
            row = self.tableWidget.rowCount()
            self.tableWidget.insertRow(row)
            for j in range(len(item)):
                item = QTableWidgetItem(str(items[i][j]))
                self.tableWidget.setItem(row,j,item)

運行結果如下:

(6) 設置表格標題字體加粗。添加代碼如下:

font = self.tableWidget.horizontalHeader().font()
font.setBold(True)
self.tableWidget.horizontalHeader().setFont(font)

運行效果如下

(7) 設置表格指定列寬。添加代碼如下:

self.tableWidget.horizontalHeader().resizeSection(0,100)
self.tableWidget.horizontalHeader().resizeSection(1,100)
self.tableWidget.horizontalHeader().resizeSection(1,100)
self.tableWidget.horizontalHeader().resizeSection(3,400)
#self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) 一定要註釋,否則上述設置不生效

說明,表頭的自適應模式要註釋,否則上述設置不生效。運行效果如下:

 (8)合併單元格,添加如下代碼

self.tableWidget.setSpan(0,3,2,1)

運行結果如下:

 這裏再補充描述一下setSpan()函數。setSpan(0,3,2,1)函數中(0,3)會指定到單元格第1行第4列(下標都是從0開始計算),(2,1)分別表示合併2行(把下一行合併了),1列(1列,列其實沒有合併)。我們把代碼修改為setSpan(1,2,3,2)。看效果如下(很直觀了吧):

(9)對齊單元格中的內容。添加代碼如下:

for i in range(len(items)):
    each_item = items[i]
    row = self.tableWidget.rowCount()
    self.tableWidget.insertRow(row)
    for j in range(len(each_item)):
        item = QTableWidgetItem(str(items[i][j]))
        if j != len(each_item) -1:
            item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.tableWidget.setItem(row,j,item)

  關鍵代碼就是item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)。前面3列居中显示,最後一列靠左显示。運行效果如下:

  另外,單元格內容對齊還有如下幾種方式。

  Qt.AlignLeft 將單元格內容沿單元格左邊緣對齊

  Qt.AlignRight 將單元格內容沿單元格右邊緣對齊

  Qt.AlignHCenter 將單元格內容居中显示在水平方向上。

  Qt.AlignJustify 將文本在可用的空間中對齊,默認是從左到右的

  Qt.AlignTop 與頂部對齊

  Qt.AlignBottom 與底部對齊

  Qt.AlignVCenter 在可用的空間中,居中显示在垂直方向上

  Qt.AlignBaseline 與基線對齊

(10)設置單元格字體顏色和背景顏色。添加代碼如下:

for i in range(len(items)):
    each_item = items[i]
    row = self.tableWidget.rowCount()
    self.tableWidget.insertRow(row)
    for j in range(len(each_item)):
        item = QTableWidgetItem(str(items[i][j]))
        if j != len(each_item) -1:
            item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            item.setForeground(QBrush(QColor(255,0,0))) #設置除最後一列外的文字顏色為紅色 else:
            item.setBackground(QBrush(QColor(0,255,0))) #設置最後一列的背景色為綠色
        self.tableWidget.setItem(row,j,item)

運行結果如下所示:

 QTableWidget控件的基本使用方法就到這裏,後續有其他更好的使用技巧會繼續更新,為方便自己及大家閱讀本文,附本文使用到的代碼如下:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'connect_me.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
#導入程序運行必須模塊
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QColor, QBrush
from PyQt5.QtCore import Qt
from cc import Ui_widget


class MyMainForm(QMainWindow, Ui_widget):
    def __init__(self, parent=None):
        super(MyMainForm, self).__init__(parent)
        self.setupUi(self)
        #self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.tableWidget.setSelectionBehavior(QAbstractItemView.SelectRows)
        #self.tableWidget.horizontalHeader().setVisible(False)
        self.tableWidget.verticalHeader().setVisible(False)
        font = self.tableWidget.horizontalHeader().font()
        font.setBold(True)
        self.tableWidget.horizontalHeader().setFont(font)
        self.tableWidget.horizontalHeader().resizeSection(0,100)
        self.tableWidget.horizontalHeader().resizeSection(1,100)
        self.tableWidget.horizontalHeader().resizeSection(1,100)
        self.tableWidget.horizontalHeader().resizeSection(3,400)
        #self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        items = [['燕十三','21','Male','武林大俠'],['蕭十一郎','21','Male','武功好']]
        for i in range(len(items)):
            each_item = items[i]
            row = self.tableWidget.rowCount()
            self.tableWidget.insertRow(row)
            for j in range(len(each_item)):
                item = QTableWidgetItem(str(items[i][j]))
                if j != len(each_item) -1:
                    item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
                    item.setForeground(QBrush(QColor(255,0,0)))
                else:
                    item.setBackground(QBrush(QColor(0,255,0)))
                self.tableWidget.setItem(row,j,item)

        self.tableWidget.setSpan(1,2,3,2)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWin = MyMainForm()
    myWin.show()
    sys.exit(app.exec_())

 總結

  本文描述PyQt5的QTableWidget控件的基本使用方法和效果,界面實現建議還是desiger設計工具實現,同時使用業務和邏輯相分離的方式編寫程序,都揉在一塊後面程序就不好維護,畢竟沒有誰的工具實現是可以一蹴而就的,還是要考慮可維護性。讀完本文內容相信讀者就可以上手使用QTableWidget控件了。。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

敏捷開發:我在路上

略有耳聞

行業變化真的很快~思想更新迭代更是應接不暇。

我在最早最早聽到敏捷開發的時候是2014年,入行剛剛兩年。

猶記得當初領導引出這個話題,大家討論開來。

“咱們敏捷不起來,那是外國玩的東西”

“敏捷就是快,極限編程,到時候代碼都是坑。還得重構”

其實很多對話已經很模糊了。但是整體的氛圍就是,想玩會把自己玩死。

當時作為一顆小白菜的我~,完全聽不懂他們在講什麼。只能從字面意義去暢想。

敏捷–就是快速的意思唄,快就對了。

極限–是不是就是給你個需求你能超出極限干出來。

這太恐怖了。後面這個話題慢慢就不知不覺中煙消雲散

痛苦

對敏捷的認識,我的思想依舊停留在之前的認知。一直沒有人討論,也沒有過自己主動補充。

主要原因,周圍的人習慣了這種跨度長,按部就班的迭代方式。

即便是有人提出過異議,依舊還是寡不敵眾,重回其道。

2017年11月 到 2019年元旦,這是我感觸頗深的一段時間。

因為分組原因,起初有一個很不起眼的系統放在了我們組,然後這個系統從無到有,我們進行快速開發上線。直到我一個人維護這個系統兩年。

後面因為我還有另外一個重要的工作,使得這兩個工作項,在衝突中迭代,在痛苦中來回切換。

其中的痛苦對於沒有管理經驗的我來說真的是煉獄。

我向管理層提出了我的想法,是不是可以改變一下這種節奏?

通過深思熟慮我從.NET組轉到了php組(可以理解為也是技術棧的完全切換),就是我們說的轉語言。

但是轉過的我,依舊痛苦。需求不斷,我依舊使用C#迭代着這個內部系統。

10月份接到一個高層領導們提出很多需求,準備一個大版本迭代。必須在元旦前上線。

首先:

人不夠 — 找外包和我一起來做

時間長 — 砍掉估時的一半,加班做

資金 — 外包2人,算是增加了預算

結果可想而知:

好在上線了(元旦加了幾天班,一個外包沒來,另一個最後一天因為胃不舒服回家了)

我現在想起元旦自己一個人在一個項目群里回復着4~5個測試(系統測試、性能測試),

一個人改着bug,改完bug列表,刷新后馬上又多了幾個bug的崩潰。

php組的領導也確實幫不上忙,默默陪着我,協調資源和處理其他問題。多虧領導的陪伴,要不我真的能放棄。

這個時候,我的小孩出生也有4個多月了。

那一年的8月,也就是2018年8月,我從老東家離職。

我給我的理由是:離家遠,想早點回家看孩子。

這個理由真的很牽強。我真的感覺倦了,感覺無休止地看不到頭,感覺自己更加迷茫。

反思

我從上一家公司離職后,到現在一年多。我才慢慢體會到我所說的痛苦都是有原因的,而且完全可以避免和克服。

入職新公司,參与了一個項目,並嘗試着管理一個項目。公司有整個項目周期的管理流程。

我從流程中學習如何管理項目。經過一年多的學習和轉變,我學習着分析當初我的痛苦。

沒有項目管理經驗的我

因為我最熟悉代碼和業務,所以組織外包分配任務。但是沒有經驗和想法的我把這個項目管理得一團糟。

我應該可以更加清晰地分配任務,使得任務相對獨立。

我也可以更加詳細地拆分任務,因為我對邏輯非常熟悉,所以可以將複雜操作拆得更加詳細

我在項目中開發,無法脫身,完全可以從上層角度來提前協調資源。

我當時的技術面比較窄,無法從更高的技術角度看代項目。

我知道當時的外包很貴,領導可能出於預算,分析了任務量,確認了2個外包。

整個開發沒有層次,測試都在最後一擁而上,我們不得不在群里說著這個功能的實現細節。然後測試再去測試。

對這個項目預估不足

沒有預估到這個內部系統如此複雜的業務纏繞

沒有預估到這個系統整個迭代如此混亂,沒有節奏,沒有章法。

心態

一開始我就輸了,輸在了心態

我總是想着2年的系統沒有文檔,重構是完不成的。

我總是想着完不成也有理由,因為A,B,C

遇到困難或在極其艱難的時候,沒有正面困難的勇氣,我選擇了抱怨和唉聲嘆氣,我選擇了消極應戰。對,我的士氣確實沒有了。

如果

如果,再有如果,我使用一些項目管理的方法和在實踐中總結的方法,再次迭代這個項目,那結果會是怎樣?

如果我再負責一點,把模塊拆開,任務分細,即便是外包來做,也不會被項目嚇到?

如果我在開始前,做了詳細的項目迭代規劃,可以先交付什麼,后交付什麼,前後沒有大的關聯。測試資源可以儘早介入。

如果我在開發前,做好風險準備以及應對方案,是不是開發中有時候就不會那麼被動?

我總結就是:層次、心性、管理

為什麼是這三個詞,這也是我覺得我從一個普通程序員轉變成初級管理的一個總結。

層次:我當初壓根就沒有轉管理的這根線,所以分析問題都是從自身角度,層次可想而知。

心性:做好了轉管理的準備,心性也要做好準備,遇到棘手的問題,客戶的催促。我必須放下抱怨、冷靜分析選擇最合適的解決方案。

管理:我思想和心裏都做好了準備,我確實需要一些指導,比如老領導的幫帶,一些書籍的閱讀。從認知上再次提升

       剩下的就是在實踐中不斷打磨自己的認知和理解,總結后再嘗試。

重新定義自己

2018年8月中旬來到現在的公司,這裏我接觸了一些項目管理的流程。

我嘗試管理項目,我嘗試總結問題,我嘗試全局分析。

這一年我犯了很多錯,回過頭髮現當初的自己是多麼幼稚不堪。

還好,我在同事和領導身上,慢慢學習他們的優點和經驗。

如何管理項目、把控流程、協調資源、拆分任務

如何和上級溝通

如何和同事更好協作

如何把自己身上的任務合理分下去,同時關注帶的人的成長

比如購銷合同一個緊急項目,如何跨部門協調,如何在緊急情況下做出合適的方案並協調資源。

... ...

重新認識敏捷

2019年3月,我們部門來了一個新同事,了解到他之前公司一直是敏捷開發。

我們時不時一起討論敏捷開發等相關問題。

“我們的任務是儘早持續交付有價值的軟件,並讓用戶滿意”

這一敏捷宣言,細細品味,確實蘊藏了巨大的能量。

圍繞着這一句話,我們可以想象到很多的方面進行改進,以接近這一宣言。

用戶為中心

價值導向

持續集成

優先級

自主管理

協作溝通

以人為本

等......

我們所使用的這些方法和策略,就是在慢慢打造更高效的團隊。發揮價值。

發揮價值,然後慢慢改造流程,發揮更大的價值。

就是在一個循環往複中,螺旋上升。

起初會有不適應,因為人都是有惰性的,組織和規範都是有平衡的。

敏捷的這些思想,無時無刻地衝擊着這些人性、組織以及規範。

當在堅持實行的過程中,信任他人,成就他人,這樣慢慢激發人性的能量。團隊收穫的是能量,個人收穫的是成長。

堅持實行敏捷,是一項艱巨的任務。這需要團隊不斷磨合,不斷找到合適的相處方式,找到每個人的能力成長點,並激發它。

敏捷最終落腳的地方是人,所以如何將敏捷這些思想,灌輸給團隊。然後沿着方法論嘗試、總結、修改、再嘗試。

這樣的敏捷,我不確定是不是也是敏捷的一種。

總結

不斷實踐,不斷吸收,不斷激發,不斷優化

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

K8S入門系列之集群二進制部署–> master篇(二)

組件版本和配置策略

組件版本

  • Kubernetes 1.16.2
  • Docker 19.03-ce
  • Etcd 3.3.17 https://github.com/etcd-io/etcd/releases/
  • Flanneld 0.11.0 https://github.com/coreos/flannel/releases/

  • 插件:

  • 鏡像倉庫:
    docker registry
    harbor

主要配置策略

  • kube-apiserver:
    使用 keepalived 和 haproxy 實現 3 節點高可用;
    關閉非安全端口 8080 和匿名訪問;
    在安全端口 6443 接收 https 請求;
    嚴格的認證和授權策略 (x509、token、RBAC);
    開啟 bootstrap token 認證,支持 kubelet TLS bootstrapping;
    使用 https 訪問 kubelet、etcd,加密通信;

  • kube-controller-manager:
    3 節點高可用;主備備
    關閉非安全端口,在安全端口 10252 接收 https 請求;
    使用 kubeconfig 訪問 apiserver 的安全端口;
    自動 approve kubelet 證書籤名請求 (CSR),證書過期后自動輪轉;
    各 controller 使用自己的 ServiceAccount 訪問 apiserver;

  • kube-scheduler:
    3 節點高可用;主備備
    使用 kubeconfig 訪問 apiserver 的安全端口;

  • kubelet:
    使用 kubeadm 動態創建 bootstrap token,也可以在 apiserver 中靜態配置;
    使用 TLS bootstrap 機制自動生成 client 和 server 證書,過期后自動輪轉;
    在 KubeletConfiguration 類型的 JSON 文件配置主要參數;
    關閉只讀端口,在安全端口 10250 接收 https 請求,對請求進行認證和授權,拒絕匿名訪問和非授權訪問;
    使用 kubeconfig 訪問 apiserver 的安全端口;

  • kube-proxy:
    使用 kubeconfig 訪問 apiserver 的安全端口;
    在 KubeProxyConfiguration 類型的 JSON 文件配置主要參數;
    使用 ipvs 代理模式;

  • 集群插件:

1. 系統初始化

1.01 系統環境&&基本環境配置

[root@localhost ~]# uname -a
Linux localhost.localdomain 4.18.0-80.11.2.el8_0.x86_64 #1 SMP Tue Sep 24 11:32:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
[root@localhost ~]# cat /etc/redhat-release 
CentOS Linux release 8.0.1905 (Core) 

1.02 修改各個節點的對應hostname, 並分別寫入/etc/hosts

hostnamectl set-hostname k8s-master01
...
# 寫入hosts--> 注意是 >> 表示不改變原有內容追加!
cat>> /etc/hosts <<EOF

192.168.2.201 k8s-master01
192.168.2.202 k8s-master02
192.168.2.203 k8s-master03
192.168.2.11 k8s-node01
192.168.2.12 k8s-node02
EOF

1.03 安裝依賴包和常用工具

yum install wget vim yum-utils net-tools tar chrony curl jq ipvsadm ipset conntrack iptables sysstat libseccomp -y

1.04 所有節點關閉firewalld, dnsmasq, selinux以及swap

# 關閉防火牆並清空防火牆規則
systemctl disable firewalld && systemctl stop firewalld && systemctl status firewalld
iptables -F && iptables -X && iptables -F -t nat && iptables -X -t nat
iptables -P FORWARD ACCEP

# 關閉dnsmasq否則可能導致docker容器無法解析域名!(centos8不存在!)
systemctl disable --now dnsmasq 

# 關閉selinux  --->selinux=disabled 需重啟生效!
setenforce 0 && sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

# 關閉swap --->註釋掉swap那一行, 需重啟生效!
swapoff -a && sed -i '/ swap / s/^\(.*\)$/# \1/g' /etc/fstab

1.05 所有節點設置時間同步

timedatectl set-timezone Asia/Shanghai
timedatectl set-local-rtc 0

yum install chrony -y
systemctl enable chronyd && systemctl start chronyd && systemctl status chronyd

1.06 調整內核參數, k8s必備參數!

# 先加載模塊
modprobe br_netfilter
cat> kubernetes.conf <<EOF
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv6.conf.all.disable_ipv6=1
net.netfilter.nf_conntrack_max = 6553500
net.nf_conntrack_max = 6553500
net.ipv4.tcp_max_tw_buckets = 4096
EOF
  • net.bridge.bridge-nf-call-iptables=1 二層的網橋在轉發包時也會被iptables的FORWARD規則所過濾
  • net.ipv6.conf.all.disable_ipv6=1 禁用整個系統所有的ipv6接口, 預防觸發docker的bug
  • net.netfilter.nf_conntrack_max 這個默認值是65535,當服務器上的連接超過這個數的時候,系統會將數據包丟掉,直到小於這個值或達到過期時間net.netfilter.nf_conntrack_tcp_timeout_established,默認值432000,5天。期間的數據包都會丟掉。
  • net.ipv4.tcp_max_tw_buckets 這個默認值18000,服務器TIME-WAIT狀態套接字的數量限制,如果超過這個數量, 新來的TIME-WAIT套接字會直接釋放。過多的TIME-WAIT影響服務器性能,根據服務自行設置.
cp kubernetes.conf  /etc/sysctl.d/kubernetes.conf
sysctl -p /etc/sysctl.d/kubernetes.conf

1.07 所有節點創建k8s工作目錄並設置環境變量!

# 在每台機器上創建目錄:
mkdir -p /opt/k8s/{bin,cert,script,kube-apiserver,kube-controller-manager,kube-scheduler,kubelet,kube-proxy}
mkdir -p /opt/etcd/{bin,cert}
mkdir -p /opt/lib/etcd
mkdir -p /opt/flanneld/{bin,cert}
mkdir -p /root/.kube
mkdir -p /var/log/kubernetes

# 在每台機器上添加環境變量:
sh -c "echo 'PATH=/opt/k8s/bin:/opt/etcd/bin:/opt/flanneld/bin:$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile.d/k8s.sh"

source /etc/profile.d/k8s.sh

1.08 無密碼 ssh 登錄其它節點(為了部署方便!!!)

生成秘鑰對

[root@k8s-master01 ~]# ssh-keygen

將自己的公鑰發給其他服務器

[root@k8s-master01 ~]# ssh-copy-id root@k8s-master01
[root@k8s-master01 ~]# ssh-copy-id root@k8s-master02
[root@k8s-master01 ~]# ssh-copy-id root@k8s-master03

2. 創建CA根證書和密鑰

  • 為確保安全, kubernetes 系統各組件需要使用 x509 證書對通信進行加密和認證。
  • CA (Certificate Authority) 是自簽名的根證書,用來簽名後續創建的其它證書。

2.01 安裝cfssl工具集

[root@k8s-master01 ~]# wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
[root@k8s-master01 ~]# mv cfssl_linux-amd64 /opt/k8s/bin/cfssl

[root@k8s-master01 ~]# wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
[root@k8s-master01 ~]# mv cfssljson_linux-amd64 /opt/k8s/bin/cfssljson

[root@k8s-master01 ~]# wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
[root@k8s-master01 ~]# mv cfssl-certinfo_linux-amd64 /opt/k8s/bin/cfssl-certinfo

chmod +x /opt/k8s/bin/*

2.02 創建根證書CA

  • CA 證書是集群所有節點共享的,只需要創建一個CA證書,後續創建的所有證書都由它簽名。

2.02.01 創建配置文件

  • CA 配置文件用於配置根證書的使用場景 (profile) 和具體參數 (usage,過期時間、服務端認證、客戶端認證、加密等),後續在簽名其它證書時需要指定特定場景。
[root@k8s-master01 ~]# cd /opt/k8s/cert/
[root@k8s-master01 cert]# cat> ca-config.json <<EOF
{
    "signing": {
        "default": {
            "expiry": "876000h"
        },
        "profiles": {
            "kubernetes": {
                "usages": [
                    "signing",
                    "key encipherment",
                    "server auth",
                    "client auth"
                ],
                "expiry": "876000h"
            }
        }
    }
}
EOF
  • signing :表示該證書可用於簽名其它證書,生成的 ca.pem 證書中CA=TRUE;
  • server auth :表示 client 可以用該該證書對 server 提供的證書進行驗證;
  • client auth :表示 server 可以用該該證書對 client 提供的證書進行驗證;

2.02.02 創建證書籤名請求文件

[root@k8s-master01 cert]# cat > ca-csr.json <<EOF
{
    "CN": "kubernetes",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "k8s",
            "OU": "steams"
        }
    ]
}
EOF
  • CN: Common Name ,kube-apiserver 從證書中提取該字段作為請求的用戶名(User Name),瀏覽器使用該字段驗證網站是否合法;
  • O: Organization ,kube-apiserver 從證書中提取該字段作為請求用戶所屬的組(Group);
  • kube-apiserver 將提取的 User、Group 作為 RBAC 授權的用戶標識;

2.02.03 生成CA證書和密鑰

[root@k8s-master01 cert]# cfssl gencert -initca ca-csr.json | cfssljson -bare ca

# 查看是否生成!
[root@k8s-master01 cert]# ls
ca-config.json  ca.csr  ca-csr.json  ca-key.pem  ca.pem

2.02.04 分發證書文件

  • 簡單腳本, 注意傳參! 後期想寫整合腳本的話可以拿來用!
  • 將生成的 CA 證書、秘鑰文件、配置文件拷貝到所有節點的/opt/k8s/cert 目錄下:
[root@k8s-master01 cert]# vi /opt/k8s/script/scp_k8s_cacert.sh 

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
    scp /opt/k8s/cert/ca*.pem /opt/k8s/cert/ca-config.json root@${master_ip}:/opt/k8s/cert
done
[root@k8s-master01 cert]# bash /opt/k8s/script/scp_k8s_cacert.sh 192.168.2.201 192.168.2.202 192.168.2.203

3. 部署etcd集群

  • etcd 是基於Raft的分佈式key-value存儲系統,由CoreOS開發,常用於服務發現、共享配置以及併發控制(如leader選舉、分佈式鎖等)
  • kubernetes 使用 etcd 存儲所有運行數據。所以部署三節點高可用!

3.01 下載二進制文件

[root@k8s-master01 ~]# wget https://github.com/etcd-io/etcd/releases/download/v3.3.17/etcd-v3.3.17-linux-amd64.tar.gz
[root@k8s-master01 ~]# tar -xvf etcd-v3.3.17-linux-amd64.tar.gz 

3.02 創建etcd證書和密鑰

  • etcd集群要與k8s–>apiserver通信, 所以需要用證書籤名驗證!

3.02.01 創建證書籤名請求

[root@k8s-master01 cert]# cat > /opt/etcd/cert/etcd-csr.json <<EOF
{
    "CN": "etcd",
    "hosts": [
        "127.0.0.1",
        "192.168.2.201",
        "192.168.2.202",
        "192.168.2.203"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "k8s",
            "OU": "steams"
        }
    ]
}
EOF
  • hosts 字段指定授權使用該證書的 etcd 節點 IP 或域名列表,這裏將 etcd 集群的三個節點 IP 都列在其中

3.02.02 生成證書和私鑰

[root@k8s-master01 ~]# cfssl gencert -ca=/opt/k8s/cert/ca.pem -ca-key=/opt/k8s/cert/ca-key.pem -config=/opt/k8s/cert/ca-config.json -profile=kubernetes /opt/etcd/cert/etcd-csr.json | cfssljson -bare /opt/etcd/cert/etcd

# 查看是否生成!
[root@k8s-master01 ~]# ls /opt/etcd/cert/*
etcd.csr       etcd-csr.json  etcd-key.pem   etcd.pem  

3.02.03 分發生成的證書, 私鑰和etcd安裝文件到各etcd節點

[root@k8s-master01 ~]# vi /opt/k8s/script/scp_etcd.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        scp /root/etcd-v3.3.17-linux-amd64/etcd* root@${master_ip}:/opt/etcd/bin
        ssh root@${master_ip} "chmod +x /opt/etcd/bin/*"
        scp /opt/etcd/cert/etcd*.pem root@${master_ip}:/opt/etcd/cert/
done
[root@k8s-master01 ~]# bash /opt/k8s/script/scp_etcd.sh 192.168.2.201 192.168.2.202 192.168.2.203

3.03 創建etcd的systemd unit模板及etcd配置文件

創建etcd的systemd unit模板

[root@k8s-master01 ~]# vi /opt/etcd/etcd.service.template

[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target
Documentation=https://github.com/coreos

[Service]
User=root
Type=notify
WorkingDirectory=/opt/lib/etcd/
ExecStart=/opt/etcd/bin/etcd \
    --data-dir=/opt/lib/etcd \
    --name ##ETCD_NAME## \
    --cert-file=/opt/etcd/cert/etcd.pem \
    --key-file=/opt/etcd/cert/etcd-key.pem \
    --trusted-ca-file=/opt/k8s/cert/ca.pem \
    --peer-cert-file=/opt/etcd/cert/etcd.pem \
    --peer-key-file=/opt/etcd/cert/etcd-key.pem \
    --peer-trusted-ca-file=/opt/k8s/cert/ca.pem \
    --peer-client-cert-auth \
    --client-cert-auth \
    --listen-peer-urls=https://##MASTER_IP##:2380 \
    --initial-advertise-peer-urls=https://##MASTER_IP##:2380 \
    --listen-client-urls=https://##MASTER_IP##:2379,http://127.0.0.1:2379 \
    --advertise-client-urls=https://##MASTER_IP##:2379 \
    --initial-cluster-token=etcd-cluster-0    \
    --initial-cluster=etcd0=https://192.168.2.201:2380,etcd1=https://192.168.2.202:2380,etcd2=https://192.168.2.203:2380 \
    --initial-cluster-state=new    
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
  • 本示例用腳本替換變量–>##ETCD_NAME##, ##MASTER_IP##
  • WorkingDirectory 、 –data-dir:指定工作目錄和數據目錄為/opt/lib/etcd ,需在啟動服務前創建這個目錄;
  • –name :指定各個節點名稱,當 –initial-cluster-state 值為new時, –name的參數值必須位於–initial-cluster 列表中;
  • –cert-file 、 –key-file:etcd server 與 client 通信時使用的證書和私鑰;
  • –trusted-ca-file:簽名 client 證書的 CA 證書,用於驗證 client 證書;
  • –peer-cert-file 、 –peer-key-file:etcd 與 peer 通信使用的證書和私鑰;
  • –peer-trusted-ca-file:簽名 peer 證書的 CA 證書,用於驗證 peer 證書;

3.04 為各節點創建和分發etcd systemd unit文件

[root@k8s-master01 ~]# vi /opt/k8s/script/etcd_service.sh

ETCD_NAMES=("etcd0" "etcd1" "etcd2")
MASTER_IPS=("$1" "$2" "$3")
#替換模板文件中的變量,為各節點創建systemd unit文件
for (( i=0; i < 3; i++ ));do
        sed -e "s/##ETCD_NAME##/${ETCD_NAMES[i]}/g" -e "s/##MASTER_IP##/${MASTER_IPS[i]}/g" /opt/etcd/etcd.service.template > /opt/etcd/etcd-${MASTER_IPS[i]}.service
done
#分發生成的systemd unit和etcd的配置文件:
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        scp /opt/etcd/etcd-${master_ip}.service root@${master_ip}:/etc/systemd/system/etcd.service
done
[root@k8s-master01 ~]# bash /opt/k8s/script/etcd_service.sh 192.168.2.201 192.168.2.202 192.168.2.203

3.05 啟動etcd服務

[root@k8s-master01 ~]# vi /opt/k8s/script/etcd.sh

MASTER_IPS=("$1" "$2" "$3")
#啟動 etcd 服務
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        ssh root@${master_ip} "systemctl daemon-reload && systemctl enable etcd && systemctl start etcd"
done
#檢查啟動結果,確保狀態為 active (running)
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        ssh root@${master_ip} "systemctl status etcd|grep Active"
done
#驗證服務狀態,輸出均為healthy 時表示集群服務正常
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        ETCDCTL_API=3 /opt/etcd/bin/etcdctl \
            --endpoints=https://${master_ip}:2379 \
            --cacert=/opt/k8s/cert/ca.pem \
            --cert=/opt/etcd/cert/etcd.pem \
            --key=/opt/etcd/cert/etcd-key.pem endpoint health
done 
[root@k8s-master01 ~]# bash /opt/k8s/script/etcd.sh 192.168.2.201 192.168.2.202 192.168.2.203

4. 部署flannel網絡

  • kubernetes要求集群內各節點(包括master節點)能通過Pod網段互聯互通。flannel使用vxlan技術為各節點創建一個可以互通的Pod網絡,使用的端口為UDP 8472,需要開放該端口(如公有雲 AWS 等)。
  • flannel第一次啟動時,從etcd獲取Pod網段信息,為本節點分配一個未使用的 /24段地址,然後創建 flannel.1(也可能是其它名稱) 接口。
  • flannel將分配的Pod網段信息寫入/run/flannel/docker文件,docker後續使用這個文件中的環境變量設置docker0網橋。

4.01 下載flannel二進制文件

[root@k8s-master01 ~]# wget https://github.com/coreos/flannel/releases/download/v0.11.0/flannel-v0.11.0-linux-amd64.tar.gz
[root@k8s-master01 ~]# mkdir flanneld
[root@k8s-master01 ~]# tar -xvf flannel-v0.11.0-linux-amd64.tar.gz -C flanneld

4.02 創建flannel證書和密鑰

  • flannel從etcd集群存取網段分配信息,而etcd集群啟用了雙向x509證書認證,所以需要flanneld 生成證書和私鑰。

4.02.01 創建證書籤名請求

[root@k8s-master01 ~]# cat > /opt/flanneld/cert/flanneld-csr.json <<EOF
{
    "CN": "flanneld",
    "hosts": [],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "k8s",
            "OU": "steams"
        }
    ]
}
EOF
  • 該證書只會被 kubectl 當做 client 證書使用,所以 hosts 字段為空;

4.02.02 生成證書和密鑰

[root@k8s-master01 ~]# cfssl gencert -ca=/opt/k8s/cert/ca.pem -ca-key=/opt/k8s/cert/ca-key.pem -config=/opt/k8s/cert/ca-config.json -profile=kubernetes /opt/flanneld/cert/flanneld-csr.json | cfssljson -bare /opt/flanneld/cert/flanneld

[root@k8s-master01 ~]# ll /opt/flanneld/cert/flanneld*
flanneld.csr       flanneld-csr.json  flanneld-key.pem   flanneld.pem   

4.02.03 將flanneld二進制文件和生成的證書和私鑰分發到所有節點(包含node節點!)

[root@k8s-master01 ~]# vi /opt/k8s/script/scp_flanneld.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
    scp /root/flanneld/flanneld /root/flanneld/mk-docker-opts.sh root@${master_ip}:/opt/flanneld/bin/
    ssh root@${master_ip} "chmod +x /opt/flanneld/bin/*"
    scp /opt/flanneld/cert/flanneld*.pem root@${master_ip}:/opt/flanneld/cert
done
[root@k8s-master01 ~]# bash /opt/k8s/script/scp_flanneld.sh 192.168.2.201 192.168.2.202 192.168.2.203

4.03 向etcd 寫入集群Pod網段信息

  • flanneld當前版本(v0.11.0)不支持etcd v3,故需使用etcd v2 API寫入配置key和網段數據;
  • 特別注意etcd版本匹配!!!

向etcd 寫入集群Pod網段信息(一個節點操作就可以了!)

[root@k8s-master01 ~]# ETCDCTL_API=2 etcdctl \
--endpoints="https://192.168.2.201:2379,https://192.168.2.202:2379,https://192.168.2.203:2379" \
--ca-file=/opt/k8s/cert/ca.pem \
--cert-file=/opt/flanneld/cert/flanneld.pem \
--key-file=/opt/flanneld/cert/flanneld-key.pem \
set /atomic.io/network/config '{"Network":"10.30.0.0/16","SubnetLen": 24, "Backend": {"Type": "vxlan"}}'

# 返回如下信息(寫入的Pod網段"Network"必須是/16 段地址,必須與kube-controller-manager的--cluster-cidr參數值一致) 
{"Network":"10.30.0.0/16","SubnetLen": 24, "Backend": {"Type": "vxlan"}}

4.04 創建flanneld的systemd unit文件

[root@k8s-master01 ~]# vi /opt/flanneld/flanneld.service.template

[Unit]
Description=Flanneld overlay address etcd agent
After=network.target
After=network-online.target
Wants=network-online.target
After=etcd.service
Before=docker.service

[Service]
Type=notify
ExecStart=/opt/flanneld/bin/flanneld \
-etcd-cafile=/opt/k8s/cert/ca.pem \
-etcd-certfile=/opt/flanneld/cert/flanneld.pem \
-etcd-keyfile=/opt/flanneld/cert/flanneld-key.pem \
-etcd-endpoints=https://192.168.2.201:2379,https://192.168.2.202:2379,https://192.168.2.203:2379 \
-etcd-prefix=/atomic.io/network \
-iface=eth0
ExecStartPost=/opt/flanneld/bin/mk-docker-opts.sh -k DOCKER_NETWORK_OPTIONS -d /run/flannel/docker
Restart=on-failure

[Install]
WantedBy=multi-user.target
RequiredBy=docker.service
  • mk-docker-opts.sh腳本將分配給flanneld的Pod子網網段信息寫入/run/flannel/docker文件,後續docker啟動時使用這個文件中的環境變量配置docker0 網橋;
  • flanneld使用系統缺省路由所在的接口與其它節點通信,對於有多個網絡接口(如內網和公網)的節點,可以用-iface參數指定通信接口,如上面的eth1接口;
  • flanneld 運行時需要 root 權限;

4.05 分發flanneld systemd unit文件到所有節點,啟動並檢查flanneld服務

[root@k8s-master01 ~]# vi /opt/k8s/script/flanneld_service.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
 #分發 flanneld systemd unit 文件到所有節點
    scp /opt/flanneld/flanneld.service.template root@${master_ip}:/etc/systemd/system/flanneld.service
 #啟動 flanneld 服務
    ssh root@${master_ip} "systemctl daemon-reload && systemctl enable flanneld && systemctl restart flanneld"
 #檢查啟動結果
    ssh root@${master_ip} "systemctl status flanneld|grep Active"
done
[root@k8s-master01 ~]# bash /opt/k8s/script/flanneld_service.sh 192.168.2.201 192.168.2.202 192.168.2.203

4.06 檢查分配給各 flanneld 的 Pod 網段信息

# 查看集群 Pod 網段(/16)
[root@k8s-master01 ~]# ETCDCTL_API=2 etcdctl \
--endpoints="https://192.168.2.201:2379,https://192.168.2.202:2379,https://192.168.2.203:2379" \
--ca-file=/opt/k8s/cert/ca.pem \
--cert-file=/opt/flanneld/cert/flanneld.pem \
--key-file=/opt/flanneld/cert/flanneld-key.pem \
get /atomic.io/network/config
# 輸出:
{"Network":"10.30.0.0/16","SubnetLen": 24, "Backend": {"Type": "vxlan"}}

# 查看已分配的 Pod 子網段列表(/24)
[root@k8s-master01 ~]# ETCDCTL_API=2 etcdctl \
--endpoints="https://192.168.2.201:2379,https://192.168.2.202:2379,https://192.168.2.203:2379" \
--ca-file=/opt/k8s/cert/ca.pem \
--cert-file=/opt/flanneld/cert/flanneld.pem \
--key-file=/opt/flanneld/cert/flanneld-key.pem \
ls /atomic.io/network/subnets
# 輸出:
/atomic.io/network/subnets/10.30.34.0-24
/atomic.io/network/subnets/10.30.41.0-24
/atomic.io/network/subnets/10.30.7.0-24

# 查看某一 Pod 網段對應的節點 IP 和 flannel 接口地址
[root@k8s-master01 ~]# ETCDCTL_API=2 etcdctl \
--endpoints="https://192.168.2.201:2379,https://192.168.2.202:2379,https://192.168.2.203:2379" \
--ca-file=/opt/k8s/cert/ca.pem \
--cert-file=/opt/flanneld/cert/flanneld.pem \
--key-file=/opt/flanneld/cert/flanneld-key.pem \
get /atomic.io/network/subnets/10.30.34.0-24
# 輸出:
{"PublicIP":"192.168.2.202","BackendType":"vxlan","BackendData":{"VtepMAC":"e6:b2:85:07:9f:c0"}}

# 驗證各節點能通過 Pod 網段互通, 注意輸出的pod網段!
[root@k8s-master01 ~]# vi /opt/k8s/script/ping_flanneld.sh
MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
 #在各節點上部署 flannel 后,檢查是否創建了 flannel 接口(名稱可能為 flannel0、flannel.0、flannel.1 等)
    ssh ${master_ip} "/usr/sbin/ip addr show flannel.1|grep -w inet"
 #在各節點上 ping 所有 flannel 接口 IP,確保能通
    ssh ${master_ip} "ping -c 1 10.30.34.0"
    ssh ${master_ip} "ping -c 1 10.30.41.0"
    ssh ${master_ip} "ping -c 1 10.30.7.0"
done
# 運行!
[root@k8s-master01 ~]# bash /opt/k8s/script/ping_flanneld.sh 192.168.2.201 192.168.2.202 192.168.2.203

5. 部署kubectl命令行工具

  • kubectl 是 kubernetes 集群的命令行管理工具
  • kubectl 默認從 ~/.kube/config 文件讀取 kube-apiserver 地址、證書、用戶名等信息,如果沒有配置,執行 kubectl 命令時可能會出錯:
  • 本文檔只需要部署一次,生成的 kubeconfig 文件與機器無關。

5.01 下載kubectl二進制文件, 並分發所有節點(包含node!)

  • kubernetes-server-linux-amd64.tar.gz包含所有組件!
[root@k8s-master01 ~]# wget https://dl.k8s.io/v1.16.2/kubernetes-server-linux-amd64.tar.gz

[root@k8s-master01 ~]# tar -zxvf kubernetes-server-linux-amd64.tar.gz
[root@k8s-master01 ~]# vi /opt/k8s/script/kubectl_environment.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
    scp /root/kubernetes/server/bin/kubectl root@${master_ip}:/opt/k8s/bin/
done
[root@k8s-master01 ~]# bash /opt/k8s/script/kubectl_environment.sh 192.168.2.201 192.168.2.202 192.168.2.203

5.02 創建 admin 證書和私鑰

  • kubectl 與 apiserver https 安全端口通信,apiserver 對提供的證書進行認證和授權。
  • kubectl 作為集群的管理工具,需要被授予最高權限。這裏創建具有最高權限的admin 證書。

創建證書籤名請求

[root@k8s-master01 ~]# cat > /opt/k8s/cert/admin-csr.json <<EOF
{
    "CN": "admin",
    "hosts": [],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "system:masters",
            "OU": "steams"
        }
    ]
}
EOF
  • O 為 system:masters ,kube-apiserver 收到該證書後將請求的 Group 設置為system:masters;
  • 預定義的 ClusterRoleBinding cluster-admin 將 Group system:masters 與Role cluster-admin 綁定,該 Role 授予所有 API的權限;
  • 該證書只會被 kubectl 當做 client 證書使用,所以 hosts 字段為空;

生成證書和私鑰

[root@k8s-master01 ~]# cfssl gencert -ca=/opt/k8s/cert/ca.pem \
-ca-key=/opt/k8s/cert/ca-key.pem \
-config=/opt/k8s/cert/ca-config.json \
-profile=kubernetes /opt/k8s/cert/admin-csr.json | cfssljson -bare /opt/k8s/cert/admin

[root@k8s-master01 ~]# ll /opt/k8s/cert/admin*
admin.csr       admin-csr.json  admin-key.pem   admin.pem  

5.03 創建和分發 kubeconfig 文件

5.03.01 創建kubeconfig文件

  • kubeconfig 為 kubectl 的配置文件,包含訪問 apiserver 的所有信息,如 apiserver 地址、CA 證書和自身使用的證書;

step.1 設置集群參數, –server=${KUBE_APISERVER}, 指定IP和端口; 本文使用的是haproxy的VIP和端口;如果沒有haproxy代理,就用實際服務的IP和端口!

[root@k8s-master01 ~]# kubectl config set-cluster kubernetes \
--certificate-authority=/opt/k8s/cert/ca.pem \
--embed-certs=true \
--server=https://192.168.2.210:8443 \
--kubeconfig=/root/.kube/kubectl.kubeconfig

step.2 設置客戶端認證參數

[root@k8s-master01 ~]# kubectl config set-credentials kube-admin \
--client-certificate=/opt/k8s/cert/admin.pem \
--client-key=/opt/k8s/cert/admin-key.pem \
--embed-certs=true \
--kubeconfig=/root/.kube/kubectl.kubeconfig

step.3 設置上下文參數

[root@k8s-master01 ~]#  kubectl config set-context kube-admin@kubernetes \
--cluster=kubernetes \
--user=kube-admin \
--kubeconfig=/root/.kube/kubectl.kubeconfig

step.4設置默認上下文

[root@k8s-master01 ~]# kubectl config use-context kube-admin@kubernetes --kubeconfig=/root/.kube/kubectl.kubeconfig

–certificate-authority :驗證 kube-apiserver 證書的根證書;
–client-certificate 、 –client-key :剛生成的 admin 證書和私鑰,連接 kube-apiserver 時使用;
–embed-certs=true :將 ca.pem 和 admin.pem 證書內容嵌入到生成的kubectl.kubeconfig 文件中(不加時,寫入的是證書文件路徑);

5.03.02 驗證kubeconfig文件

[root@k8s-master01 ~]# kubectl config view --kubeconfig=/root/.kube/kubectl.kubeconfig
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.2.210:8443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kube-admin
  name: kube-admin@kubernetes
current-context: kube-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kube-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

5.03.03 分發 kubeclt 和kubeconfig 文件,分發到所有使用kubectl 命令的節點

[root@k8s-master01 ~]# vi /opt/k8s/script/scp_kubectl_config.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
    scp /root/kubernetes/server/bin/kubectl root@${master_ip}:/opt/k8s/bin/
    ssh root@${master_ip} "chmod +x /opt/k8s/bin/*"
    scp /root/.kube/kubectl.kubeconfig root@${master_ip}:/root/.kube/config
done
[root@k8s-master01 ~]# bash /opt/k8s/script/scp_kubectl_config.sh 192.168.2.201 192.168.2.202 192.168.2.203

6. 部署master節點

  • kubernetes master 節點運行如下組件:
    kube-apiserver
    kube-scheduler
    kube-controller-manager

  • kube-scheduler 和 kube-controller-manager 可以以集群模式運行,通過 leader 選舉產生一個工作進程,其它進程處於阻塞模式。
  • 對於 kube-apiserver,可以運行多個實例, 但對其它組件需要提供統一的訪問地址,該地址需要高可用。本文檔使用 keepalived 和 haproxy 實現 kube-apiserver VIP 高可用和負載均衡。
  • 因為對master做了keepalived高可用,所以3台服務器都有可能會升成master服務器(主master宕機,會有從升級為主);因此所有的master操作,在3個服務器上都要進行。

下載最新版本的二進制文件, 想辦法!!!

[root@k8s-master01 ~]# wget https://dl.k8s.io/v1.16.2/kubernetes-server-linux-amd64.tar.gz

[root@k8s-master01 ~]# wget https://dl.k8s.io/v1.16.2/kubernetes-server-linux-amd64.tar.gz

將二進制文件拷貝到所有 master 節點

[root@k8s-master01 ~]# vi /opt/k8s/script/scp_master.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
    scp /root/kubernetes/server/bin/{kube-apiserver,kube-controller-manager,kube-scheduler} root@${master_ip}:/opt/k8s/bin/
    ssh root@${master_ip} "chmod +x /opt/k8s/bin/*"
done
[root@k8s-master01 ~]# bash /opt/k8s/script/scp_master.sh 192.168.2.201 192.168.2.202 192.168.2.203

6.01 部署高可用組件

  • 本文檔講解使用 keepalived 和 haproxy 實現 kube-apiserver 高可用的步驟:
    keepalived 提供 kube-apiserver 對外服務的 VIP;
    haproxy 監聽 VIP,後端連接所有 kube-apiserver 實例,提供健康檢查和負載均衡功能;
  • 運行 keepalived 和 haproxy 的節點稱為 LB 節點。由於 keepalived 是一主多備運行模式,故至少兩個 LB 節點。
  • 本文檔復用 master 節點的三台機器,haproxy 監聽的端口(8443) 需要與 kube-apiserver的端口 6443 不同,避免衝突。
  • keepalived 在運行過程中周期檢查本機的 haproxy 進程狀態,如果檢測到 haproxy 進程異常,則觸發重新選主的過程,VIP 將飄移到新選出來的主節點,從而實現 VIP 的高可用。
  • 所有組件(如 kubeclt、apiserver、controller-manager、scheduler 等)都通過 VIP 和haproxy 監聽的 8443 端口訪問 kube-apiserver 服務。

6.01.01 安裝軟件包,配置haproxy 配置文件

[root@k8s-master01 ~]# yum install keepalived haproxy -y

[root@k8s-master01 ~]# vi /etc/haproxy/haproxy.cfg 
global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /var/run/haproxy-admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    nbproc 1
defaults
    log global
    timeout connect 5000
    timeout client 10m
    timeout server 10m
listen admin_stats
    bind 0.0.0.0:10080
    mode http
    log 127.0.0.1 local0 err
    stats refresh 30s
    stats uri /status
    stats realm welcome login\ Haproxy
    stats auth haproxy:123456
    stats hide-version
    stats admin if TRUE
listen k8s-master
    bind 0.0.0.0:8443
    mode tcp
    option tcplog
    balance source
    server 192.168.2.201 192.168.2.201:6443 check inter 2000 fall 2 rise 2 weight 1
    server 192.168.2.202 192.168.2.202:6443 check inter 2000 fall 2 rise 2 weight 1
    server 192.168.2.203 192.168.2.203:6443 check inter 2000 fall 2 rise 2 weight 1
  • haproxy 在 10080 端口輸出 status 信息;
  • haproxy 監聽所有接口的 8443 端口,該端口與環境變量 ${KUBE_APISERVER} 指定的端口必須一致;
  • server 字段列出所有kube-apiserver監聽的 IP 和端口;

6.01.02 在其他服務器安裝、下發haproxy 配置文件;並啟動檢查haproxy服務

[root@k8s-master01 ~]# vi /opt/k8s/script/haproxy.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
 #安裝haproxy
    ssh root@${master_ip} "yum install -y keepalived haproxy"
 #下發配置文件
    scp /etc/haproxy/haproxy.cfg root@${master_ip}:/etc/haproxy
 #啟動檢查haproxy服務
    ssh root@${master_ip} "systemctl restart haproxy"
    ssh root@${master_ip} "systemctl enable haproxy.service"
    ssh root@${master_ip} "systemctl status haproxy|grep Active"
 #檢查 haproxy 是否監聽6443 端口
    ssh root@${master_ip} "netstat -lnpt|grep haproxy"
done
[root@k8s-master01 ~]# bash /opt/k8s/script/haproxy.sh 192.168.2.201 192.168.2.202 192.168.2.203

輸出類似:

Active: active (running) since Tue 2019-11-12 01:54:41 CST; 543ms ago
tcp        0      0 0.0.0.0:8443            0.0.0.0:*               LISTEN      4995/haproxy        
tcp        0      0 0.0.0.0:10080           0.0.0.0:*               LISTEN      4995/haproxy   

6.01.03 配置和啟動 keepalived 服務

  • keepalived 是一主(master)多備(backup)運行模式,故有兩種類型的配置文件。
  • master 配置文件只有一份,backup 配置文件視節點數目而定,對於本文檔而言,規劃如下:
    master: 192.168.2.201
    backup:192.168.2.202、192.168.2.203

在192.168.2.201 master主服務;配置文件:

[root@k8s-master01 ~]# vim /etc/keepalived/keepalived.conf

global_defs {
    router_id keepalived_ha_121
}
vrrp_script check-haproxy {
    script "killall -0 haproxy"
    interval 5
    weight -30
}
vrrp_instance VI-k8s-master {
    state MASTER
    priority 120    # 第一台從為110, 以此類推!
    dont_track_primary
    interface eth0
    virtual_router_id 121
    advert_int 3
    track_script {
        check-haproxy
    }
    virtual_ipaddress {
        192.168.2.210
    }
}
  • 我的VIP 所在的接口nterface 為 eth0;根據自己的情況改變
  • 使用 killall -0 haproxy 命令檢查所在節點的 haproxy 進程是否正常。如果異常則將權重減少(-30),從而觸發重新選主過程;
  • router_id、virtual_router_id 用於標識屬於該 HA 的 keepalived 實例,如果有多套keepalived HA,則必須各不相同;

在192.168.2.202, 192.168.2.203兩台backup 服務;配置文件:

[root@k8s-master02 ~]# vi /etc/keepalived/keepalived.conf

global_defs {
        router_id keepalived_ha_122_123
}
vrrp_script check-haproxy {
        script "killall -0 haproxy"
        interval 5
        weight -30
}
vrrp_instance VI-k8s-master {
        state BACKUP
        priority 110   # 第2台從為100
        dont_track_primary
        interface eth0
        virtual_router_id 121
        advert_int 3
        track_script {
        check-haproxy
        }
        virtual_ipaddress {
            192.168.2.210
        }
}
  • priority 的值必須小於 master 的值;兩個從的值也需要不一樣;

開啟keepalived 服務

[root@k8s-master01 ~]# systemctl restart keepalived && systemctl enable keepalived && systemctl status keepalived
[root@k8s-master01 ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:00:68:05 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.101/24 brd 192.168.2.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet 192.168.2.10/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::f726:9d22:2b89:694c/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether aa:43:e5:bb:88:28 brd ff:ff:ff:ff:ff:ff
    inet 10.30.34.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
    inet6 fe80::a843:e5ff:febb:8828/64 scope link 
       valid_lft forever preferred_lft forever
  • 在master主服務器上能看到eth0網卡上已經有VIP的IP地址存在

6.01.04 查看 haproxy 狀態頁面

  • 瀏覽器訪問 192.168.2.210:10080/status 地址

6.02 部署 kube-apiserver 組件

下載二進制文件

  • kubernetes_server 包里有, 已經解壓到/opt/k8s/bin下

6.02.01 創建 kube-apiserver證書和私鑰

創建證書籤名請求

[root@k8s-master01 ~]# cat > /opt/k8s/cert/kube-apiserver-csr.json <<EOF
{
    "CN": "kubernetes",
    "hosts": [
        "127.0.0.1",
        "192.168.2.201",
        "192.168.2.202",
        "192.168.2.203",
        "192.168.2.210",
        "10.96.0.1",
        "kubernetes",
        "kubernetes.default",
        "kubernetes.default.svc",
        "kubernetes.default.svc.cluster",
        "kubernetes.default.svc.cluster.local"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
          "C": "CN",
          "ST": "BeiJing",
          "L": "BeiJing",
          "O": "k8s",
          "OU": "steams"
        }
    ]
}
EOF
  • hosts 字段指定授權使用該證書的 IP 或域名列表,這裏列出了 VIP 、apiserver節點 IP、kubernetes 服務 IP 和域名;
  • 域名最後字符不能是 . (如不能為kubernetes.default.svc.cluster.local. ),否則解析時失敗,提示: x509:cannot parse dnsName “kubernetes.default.svc.cluster.local.” ;
  • 如果使用非 cluster.local 域名,如 opsnull.com ,則需要修改域名列表中的最後兩個域名為: kubernetes.default.svc.opsnull 、 kubernetes.default.svc.opsnull.com
  • kubernetes 服務 IP 是 apiserver 自動創建的,一般是 –service-cluster-ip-range 參數指定的網段的第一個IP,後續可以通過如下命令獲取:
    kubectl get svc kubernetes

生成證書和私鑰

[root@k8s-master01 ~]# cfssl gencert -ca=/opt/k8s/cert/ca.pem \
-ca-key=/opt/k8s/cert/ca-key.pem \
-config=/opt/k8s/cert/ca-config.json \
-profile=kubernetes /opt/k8s/cert/kube-apiserver-csr.json | cfssljson -bare /opt/k8s/cert/kube-apiserver

[root@k8s-master01 ~]# ll /opt/k8s/cert/kube-apiserver*
kube-apiserver.csr      kube-apiserver-csr.json  kube-apiserver-key.pem  kube-apiserver.pem 

6.02.02 創建加密配置文件

產生一個用來加密Etcd 的 Key:

[root@k8s-master01 ~]# head -c 32 /dev/urandom | base64
# 返回一個key, 每台master節點需要用一樣的 Key!!!
muqIUutYDd5ARLtsg/W1CYWs3g8Fq9uJO/lDpSsv9iw=

使用這個加密的key,創建加密配置文件

[root@k8s-master01 ~]# vi encryption-config.yaml

kind: EncryptionConfig
apiVersion: v1
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: muqIUutYDd5ARLtsg/W1CYWs3g8Fq9uJO/lDpSsv9iw=
      - identity: {}

6.02.03 將生成的證書和私鑰文件、加密配置文件拷貝到master節點的/opt/k8s目錄下

[root@k8s-master01 ~]# vi /opt/k8s/script/scp_apiserver.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo  ">>> ${master_ip}"
    scp /opt/k8s/cert/kube-apiserver*.pem root@${master_ip}:/opt/k8s/cert/
    scp /root/encryption-config.yaml root@${master_ip}:/opt/k8s/
done 
[root@k8s-master01 ~]# bash /opt/k8s/script/scp_apiserver.sh 192.168.2.201 192.168.2.202 192.168.2.203

6.02.04 創建 kube-apiserver 的 systemd unit 模板文件

[root@k8s-master01 ~]# vi /opt/k8s/kube-apiserver/kube-apiserver.service.template

[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
After=network.target

[Service]
ExecStart=/opt/k8s/bin/kube-apiserver \
--enable-admission-plugins=NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \
--anonymous-auth=false \
--experimental-encryption-provider-config=/opt/k8s/encryption-config.yaml \
--advertise-address=##MASTER_IP## \
--bind-address=##MASTER_IP## \
--insecure-port=0 \
--authorization-mode=Node,RBAC \
--runtime-config=api/all \
--enable-bootstrap-token-auth \
--service-cluster-ip-range=10.96.0.0/16 \
--service-node-port-range=30000-50000 \
--tls-cert-file=/opt/k8s/cert/kube-apiserver.pem \
--tls-private-key-file=/opt/k8s/cert/kube-apiserver-key.pem \
--client-ca-file=/opt/k8s/cert/ca.pem \
--kubelet-client-certificate=/opt/k8s/cert/kube-apiserver.pem \
--kubelet-client-key=/opt/k8s/cert/kube-apiserver-key.pem \
--service-account-key-file=/opt/k8s/cert/ca-key.pem \
--etcd-cafile=/opt/k8s/cert/ca.pem \
--etcd-certfile=/opt/k8s/cert/kube-apiserver.pem \
--etcd-keyfile=/opt/k8s/cert/kube-apiserver-key.pem \
--etcd-servers=https://192.168.2.201:2379,https://192.168.2.202:2379,https://192.168.2.203:2379 \
--enable-swagger-ui=true \
--allow-privileged=true \
--apiserver-count=3 \
--audit-log-maxage=30 \
--audit-log-maxbackup=3 \
--audit-log-maxsize=100 \
--audit-log-path=/var/log/kube-apiserver-audit.log \
--event-ttl=1h \
--alsologtostderr=true \
--logtostderr=false \
--log-dir=/var/log/kubernetes \
--v=2
Restart=on-failure
RestartSec=5
Type=notify
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
  • -experimental-encryption-provider-config :啟用加密特性;
  • –authorization-mode=Node,RBAC : 開啟 Node 和 RBAC 授權模式,拒絕未授權的請求;
  • –enable-admission-plugins :啟用 ServiceAccount 和NodeRestriction ;
  • –service-account-key-file :簽名 ServiceAccount Token 的公鑰文件,kube-controller-manager 的 –service-account-private-key-file 指定私鑰文件,兩者配對使用;
  • –tls-*-file :指定 apiserver 使用的證書、私鑰和 CA 文件。 –client-ca-file 用於驗證 client (kue-controller-manager、kube-scheduler、kubelet、kube-proxy 等)請求所帶的證書;
  • –kubelet-client-certificate 、 –kubelet-client-key :如果指定,則使用 https 訪問 kubelet APIs;需要為證書對應的用戶(上面 kubernetes*.pem 證書的用戶為 kubernetes) 用戶定義 RBAC 規則,否則訪問 kubelet API 時提示未授權;
  • –bind-address : 不能為 127.0.0.1 ,否則外界不能訪問它的安全端口6443;
  • –insecure-port=0 :關閉監聽非安全端口(8080);
  • –service-cluster-ip-range : 指定 Service Cluster IP 地址段;
  • –service-node-port-range : 指定 NodePort 的端口範圍;
  • –runtime-config=api/all=true : 啟用所有版本的 APIs,如autoscaling/v2alpha1;
  • –enable-bootstrap-token-auth :啟用 kubelet bootstrap 的 token 認證;
  • –apiserver-count=3 :指定集群運行模式,多台 kube-apiserver 會通過 leader選舉產生一個工作節點,其它節點處於阻塞狀態;

6.02.05 為各master節點創建和分發 kube-apiserver的systemd unit文件; 啟動檢查 kube-apiserver 服務

[root@k8s-master01 ~]# vi /opt/k8s/script/apiserver_service.sh

MASTER_IPS=("$1" "$2" "$3")
#替換模板文件中的變量,為各節點創建 systemd unit 文件
for (( i=0; i < 3; i++ ));do
    sed "s/##MASTER_IP##/${MASTER_IPS[i]}/" /opt/k8s/kube-apiserver/kube-apiserver.service.template > /opt/k8s/kube-apiserver/kube-apiserver-${MASTER_IPS[i]}.service
done
#啟動並檢查 kube-apiserver 服務
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
    scp /opt/k8s/kube-apiserver/kube-apiserver-${master_ip}.service root@${master_ip}:/etc/systemd/system/kube-apiserver.service
    ssh root@${master_ip} "systemctl daemon-reload && systemctl enable kube-apiserver && systemctl restart kube-apiserver"
    ssh root@${master_ip} "systemctl status kube-apiserver |grep 'Active:'"
done
[root@k8s-master01 ~]# bash /opt/k8s/script/apiserver_service.sh 192.168.2.201 192.168.2.202 192.168.2.203

6.02.06 打印 kube-apiserver 寫入 etcd 的數據

[root@k8s-master01 ~]# ETCDCTL_API=3 etcdctl \
--endpoints="https://192.168.2.201:2379,https://192.168.2.202:2379,https://192.168.2.203:2379" \
--cacert=/opt/k8s/cert/ca.pem \
--cert=/opt/etcd/cert/etcd.pem \
--key=/opt/etcd/cert/etcd-key.pem \
get /registry/ --prefix --keys-only

6.02.07 檢查集群信息

[root@k8s-master01 ~]# kubectl cluster-info
Kubernetes master is running at https://192.168.2.210:8443

[root@k8s-master01 ~]# kubectl get all --all-namespaces
NAMESPACE   NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
default     service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   49m

# 6443: 接收 https 請求的安全端口,對所有請求做認證和授權;
# 由於關閉了非安全端口,故沒有監聽 8080;
[root@k8s-master01 ~]# ss -nutlp |grep apiserver
tcp    LISTEN   0        128         192.168.2.201:6443           0.0.0.0:*      users:(("kube-apiserver",pid=4425,fd=6)) 

6.03 部署高可用kube-controller-manager 集群

  • 該集群包含 3 個節點,啟動后將通過競爭選舉機制產生一個 leader 節點,其它節點為阻塞狀態。當 leader 節點不可用后,剩餘節點將再次進行選舉產生新的 leader 節點,從而保證服務的可用性。
  • 為保證通信安全,本文檔先生成 x509 證書和私鑰,kube-controller-manager 在如下兩種情況下使用該證書:
    與 kube-apiserver 的安全端口通信時;
    在安全端口(https,10252) 輸出 prometheus 格式的 metrics;

準備工作:下載kube-controller-manager二進制文件(包含在kubernetes-server包里, 已解壓發送)

6.03.01 創建 kube-controller-manager 證書和私鑰

創建證書籤名請求:

[root@k8s-master01 ~]# cat > /opt/k8s/cert/kube-controller-manager-csr.json <<EOF
{
    "CN": "system:kube-controller-manager",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "hosts": [
        "127.0.0.1",
        "192.168.2.201",
        "192.168.2.202",
        "192.168.2.203"
    ],
    "names": [
        {
            "C": "CN",
            "ST": "BeiJing",
            "L": "BeiJing",
            "O": "system:kube-controller-manager",
            "OU": "steams"
        }
    ]
}
EOF
  • hosts 列表包含所有 kube-controller-manager 節點 IP;
  • CN 為 system:kube-controller-manager、O 為 system:kube-controller-manager(kubernetes 內置的 ClusterRoleBindings system:kube-controller-manager 賦予kube-controller-manager 工作所需的權限.)

生成證書和私鑰

cfssl gencert -ca=/opt/k8s/cert/ca.pem \
-ca-key=/opt/k8s/cert/ca-key.pem \
-config=/opt/k8s/cert/ca-config.json \
-profile=kubernetes /opt/k8s/cert/kube-controller-manager-csr.json | cfssljson -bare /opt/k8s/cert/kube-controller-manager

[root@k8s-master01 ~]# ll /opt/k8s/cert/kube-controller-manager*
kube-controller-manager.csr       kube-controller-manager-csr.json  kube-controller-manager-key.pem   kube-controller-manager.pem  

6.03.02 創建.kubeconfig 文件

  • kubeconfig 文件包含訪問 apiserver 的所有信息,如 apiserver 地址、CA 證書和自身使用的證書;

執行命令,生成kube-controller-manager.kubeconfig文件

# step.1 設置集群參數:
[root@k8s-master01 ~]# kubectl config set-cluster kubernetes \
--certificate-authority=/opt/k8s/cert/ca.pem \
--embed-certs=true \
--server=https://192.168.2.210:8443 \
--kubeconfig=/opt/k8s/kube-controller-manager/kube-controller-manager.kubeconfig

# step.2 設置客戶端認證參數
[root@k8s-master01 ~]# kubectl config set-credentials system:kube-controller-manager \
--client-certificate=/opt/k8s/cert/kube-controller-manager.pem \
--client-key=/opt/k8s/cert/kube-controller-manager-key.pem \
--embed-certs=true \
--kubeconfig=/opt/k8s/kube-controller-manager/kube-controller-manager.kubeconfig

# step.3 設置上下文參數
[root@k8s-master01 ~]# kubectl config set-context system:kube-controller-manager@kubernetes \
--cluster=kubernetes \
--user=system:kube-controller-manager \
--kubeconfig=/opt/k8s/kube-controller-manager/kube-controller-manager.kubeconfig

# tep.4 設置默認上下文
[root@k8s-master01 ~]# kubectl config use-context system:kube-controller-manager@kubernetes \
--kubeconfig=/opt/k8s/kube-controller-manager/kube-controller-manager.kubeconfig

驗證kube-controller-manager.kubeconfig文件

[root@k8s-master01 ~]# kubectl config view --kubeconfig=/opt/k8s/kube-controller-manager/kube-controller-manager.kubeconfig
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.2.210:8443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:kube-controller-manager
  name: system:kube-controller-manager@kubernetes
current-context: system:kube-controller-manager@kubernetes
kind: Config
preferences: {}
users:
- name: system:kube-controller-manager
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

分發生成的證書和私鑰、kubeconfig 到所有 master 節點

[root@k8s-master01 ~]# vi /opt/k8s/script/scp_controller_manager.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
    echo ">>> ${master_ip}"
    scp /opt/k8s/cert/kube-controller-manager*.pem root@${master_ip}:/opt/k8s/cert/
    scp /opt/k8s/kube-controller-manager/kube-controller-manager.kubeconfig root@${master_ip}:/opt/k8s/kube-controller-manager/
done
[root@k8s-master01 ~]# bash /opt/k8s/script/scp_controller_manager.sh 192.168.2.201 192.168.2.202 192.168.2.203

6.03.03 創建和分發 kube-controller-manager 的 systemd unit 文件

[root@k8s-master01 ~]# vi /opt/k8s/kube-controller-manager/kube-controller-manager.service.template

[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/GoogleCloudPlatform/kubernetes

[Service]
ExecStart=/opt/k8s/bin/kube-controller-manager \
--port=0 \
--secure-port=10252 \
--bind-address=127.0.0.1 \
--kubeconfig=/opt/k8s/kube-controller-manager/kube-controller-manager.kubeconfig \
--service-cluster-ip-range=10.96.0.0/16 \
--cluster-name=kubernetes \
--cluster-signing-cert-file=/opt/k8s/cert/ca.pem \
--cluster-signing-key-file=/opt/k8s/cert/ca-key.pem \
--experimental-cluster-signing-duration=8760h \
--root-ca-file=/opt/k8s/cert/ca.pem \
--service-account-private-key-file=/opt/k8s/cert/ca-key.pem \
--leader-elect=true \
--feature-gates=RotateKubeletServerCertificate=true \
--controllers=*,bootstrapsigner,tokencleaner \
--horizontal-pod-autoscaler-use-rest-clients=true \
--horizontal-pod-autoscaler-sync-period=10s \
--tls-cert-file=/opt/k8s/cert/kube-controller-manager.pem \
--tls-private-key-file=/opt/k8s/cert/kube-controller-manager-key.pem \
--use-service-account-credentials=true \
--alsologtostderr=true \
--logtostderr=false \
--log-dir=/var/log/kubernetes \
--v=2
Restart=on
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
  • –port=0:關閉監聽 http /metrics 的請求,同時 –address 參數無效,–bind-address 參數有效;
  • –secure-port=10252、–bind-address=0.0.0.0: 在所有網絡接口監聽 10252 端口的 https /metrics 請求;
  • –kubeconfig:指定 kubeconfig 文件路徑,kube-controller-manager 使用它連接和驗證 kube-apiserver;
  • –cluster-signing-*-file:簽名 TLS Bootstrap 創建的證書;
  • –experimental-cluster-signing-duration:指定 TLS Bootstrap 證書的有效期;
  • –root-ca-file:放置到容器 ServiceAccount 中的 CA 證書,用來對 kube-apiserver 的證書進行校驗;
  • –service-account-private-key-file:簽名 ServiceAccount 中 Token 的私鑰文件,必須和 kube-apiserver 的 –service-account-key-file 指定的公鑰文件配對使用;
  • –service-cluster-ip-range :指定 Service Cluster IP 網段,必須和 kube-apiserver 中的同名參數一致;
  • –leader-elect=true:集群運行模式,啟用選舉功能;被選為 leader 的節點負責處理工作,其它節點為阻塞狀態;
  • –feature-gates=RotateKubeletServerCertificate=true:開啟 kublet server 證書的自動更新特性;
  • –controllers=*,bootstrapsigner,tokencleaner:啟用的控制器列表,tokencleaner 用於自動清理過期的 Bootstrap token;
  • –horizontal-pod-autoscaler-*:custom metrics 相關參數,支持 autoscaling/v2alpha1;
  • –tls-cert-file、–tls-private-key-file:使用 https 輸出 metrics 時使用的 Server 證書和秘鑰;
  • –use-service-account-credentials=true:

6.03.04 kube-controller-manager 的權限

  • ClusteRole: system:kube-controller-manager 的權限很小,只能創建 secret、serviceaccount 等資源對象,各 controller 的權限分散到 ClusterRole system:controller:XXX 中。
  • 需要在 kube-controller-manager 的啟動參數中添加 –use-service-account-credentials=true 參數,這樣 main controller 會為各 controller 創建對應的 ServiceAccount XXX-controller。
  • 內置的 ClusterRoleBinding system:controller:XXX 將賦予各 XXX-controller ServiceAccount 對應的 ClusterRole system:controller:XXX 權限。

6.03.05 分發systemd unit 文件到所有master 節點;啟動檢查 kube-controller-manager 服務

[root@k8s-master01 ~]# vi /opt/k8s/script/controller_manager_service.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        scp /opt/k8s/kube-controller-manager/kube-controller-manager.service.template root@${master_ip}:/etc/systemd/system/kube-controller-manager.service
        ssh root@${master_ip} "systemctl daemon-reload && systemctl enable kube-controller-manager && systemctl start kube-controller-manager "
        ssh root@${master_ip} "systemctl status kube-controller-manager|grep Active"
done
[root@k8s-master01 ~]# bash /opt/k8s/script/controller_manager_service.sh 192.168.2.201 192.168.2.202 192.168.2.203

6.03.6 查看輸出的 metric

[root@k8s-master01 ~]# ss -nutlp |grep kube-controll
tcp    LISTEN   0        128             127.0.0.1:10252          0.0.0.0:*      users:(("kube-controller",pid=9382,fd=6))  

6.03.07 測試 kube-controller-manager 集群的高可用

  • 停掉一個或兩個節點的 kube-controller-manager 服務,觀察其它節點的日誌,看是否獲取了 leader 權限。
  • 查看當前的 leader
[root@k8s-master02 ~]# kubectl get endpoints kube-controller-manager --namespace=kube-system -o yaml

6.04 部署高可用 kube-scheduler 集群

  • 該集群包含 3 個節點,啟動后將通過競爭選舉機制產生一個 leader 節點,其它節點為阻塞狀態。當 leader 節點不可用后,剩餘節點將再次進行選舉產生新的 leader 節點,從而保證服務的可用性。
  • 為保證通信安全,本文檔先生成 x509 證書和私鑰,kube-scheduler 在如下兩種情況下使用該證書:
    與 kube-apiserver 的安全端口通信;
    在安全端口(https,10251) 輸出 prometheus 格式的 metrics;

準備工作:下載kube-scheduler 的二進制文件—^^^

6.04.01 創建 kube-scheduler 證書和私鑰

創建證書籤名請求:

[root@k8s-master01 ~]# cat > /opt/k8s/cert/kube-scheduler-csr.json <<EOF
{
    "CN": "system:kube-scheduler",
    "hosts": [
      "127.0.0.1",
      "192.168.2.201",
      "192.168.2.202",
      "192.168.2.203"
    ],
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
      {
        "C": "CN",
        "ST": "BeiJing",
        "L": "BeiJing",
        "O": "system:kube-scheduler",
        "OU": "steams"
      }
    ]
}
EOF
  • hosts 列表包含所有 kube-scheduler 節點 IP;
  • CN 為 system:kube-scheduler、O 為 system:kube-scheduler (kubernetes 內置的 ClusterRoleBindings system:kube-scheduler 將賦予 kube-scheduler 工作所需的權限.)

生成證書和私鑰

[root@k8s-master01 ~]# cfssl gencert -ca=/opt/k8s/cert/ca.pem \
-ca-key=/opt/k8s/cert/ca-key.pem \
-config=/opt/k8s/cert/ca-config.json \
-profile=kubernetes /opt/k8s/cert/kube-scheduler-csr.json | cfssljson -bare /opt/k8s/cert/kube-scheduler

[root@k8s-master01 ~]# ll /opt/k8s/cert/kube-scheduler*
kube-scheduler.csr       kube-scheduler-csr.json  kube-scheduler-key.pem   kube-scheduler.pem  

6.04.02 創建kubeconfig 文件

  • kubeconfig 文件包含訪問 apiserver 的所有信息,如 apiserver 地址、CA 證書和自身使用的證書;

執行命令,生成kube-scheduler.kubeconfig文件

# step.1 設置集群參數
[root@k8s-master01 ~]# kubectl config set-cluster kubernetes \
--certificate-authority=/opt/k8s/cert/ca.pem \
--embed-certs=true \
--server=https://192.168.2.210:8443 \
--kubeconfig=/opt/k8s/kube-scheduler/kube-scheduler.kubeconfig

# step.2 設置客戶端認證參數
[root@k8s-master01 ~]# kubectl config set-credentials system:kube-scheduler \
--client-certificate=/opt/k8s/cert/kube-scheduler.pem \
--client-key=/opt/k8s/cert/kube-scheduler-key.pem \
--embed-certs=true  \
--kubeconfig=/opt/k8s/kube-scheduler/kube-scheduler.kubeconfig

# step.3 設置上下文參數
[root@k8s-master01 ~]# kubectl config set-context system:kube-scheduler@kubernetes \
--cluster=kubernetes \
--user=system:kube-scheduler \
--kubeconfig=/opt/k8s/kube-scheduler/kube-scheduler.kubeconfig

# step.4設置默認上下文
[root@k8s-master01 ~]# kubectl config use-context system:kube-scheduler@kubernetes \
--kubeconfig=/opt/k8s/kube-scheduler/kube-scheduler.kubeconfig

驗證kube-controller-manager.kubeconfig文件

[root@k8s-master01 ~]# kubectl config view --kubeconfig=/opt/k8s/kube-scheduler/kube-scheduler.kubeconfig
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.2.210:8443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: system:kube-scheduler
  name: system:kube-scheduler@kubernetes
current-context: system:kube-scheduler@kubernetes
kind: Config
preferences: {}
users:
- name: system:kube-scheduler
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

6.04.03 分發生成的證書和私鑰、kubeconfig 到所有 master 節點

[root@k8s-master01 ~]# vi /opt/k8s/script/scp_scheduler.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        scp /opt/k8s/cert/kube-scheduler*.pem root@${master_ip}:/opt/k8s/cert/
        scp /opt/k8s/kube-scheduler/kube-scheduler.kubeconfig root@${master_ip}:/opt/k8s/kube-scheduler/
done
[root@k8s-master01 ~]# bash /opt/k8s/script/scp_scheduler.sh 192.168.2.201 192.168.2.202 192.168.2.203

6.04.04 創建kube-scheduler 的 systemd unit 文件

[root@k8s-master01 ~]# vi /opt/k8s/kube-scheduler/kube-scheduler.service.template

[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/GoogleCloudPlatform/kubernetes

[Service]
ExecStart=/opt/k8s/bin/kube-scheduler \
  --address=127.0.0.1 \
  --kubeconfig=/opt/k8s/kube-scheduler/kube-scheduler.kubeconfig \
  --leader-elect=true \
  --alsologtostderr=true \
  --logtostderr=false \
  --log-dir=/var/log/kubernetes \
  --v=2
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
  • ps: kube-scheduler目前僅支持http, 所以少了一大推相關安全設定!
  • –address:在 127.0.0.1:10251 端口接收 http /metrics 請求;kube-scheduler 目前還不支持接收 https 請求;
  • –kubeconfig:指定 kubeconfig 文件路徑,kube-scheduler 使用它連接和驗證 kube-apiserver;
  • –leader-elect=true:集群運行模式,啟用選舉功能;被選為 leader 的節點負責處理工作,其它節點為阻塞狀態;

6.04.05 分發systemd unit 文件到所有master 節點;啟動檢查kube-scheduler 服務

[root@k8s-master01 ~]# vi /opt/k8s/script/scheduler_service.sh

MASTER_IPS=("$1" "$2" "$3")
for master_ip in ${MASTER_IPS[@]};do
        echo ">>> ${master_ip}"
        scp /opt/k8s/kube-scheduler/kube-scheduler.service.template root@${master_ip}:/etc/systemd/system/kube-scheduler.service
        ssh root@${master_ip} "systemctl daemon-reload && systemctl enable kube-scheduler && systemctl start kube-scheduler && systemctl status kube-scheduler|grep Active"
done
[root@k8s-master01 ~]# bash /opt/k8s/script/scheduler_service.sh 192.168.2.201 192.168.2.202 192.168.2.203

6.04.06 查看輸出的 metric

  • kube-scheduler 監聽 10251 端口,接收 http 請求:
[root@k8s-master01 ~]# ss -nutlp |grep kube-scheduler
tcp    LISTEN   0        128             127.0.0.1:10251          0.0.0.0:*      users:(("kube-scheduler",pid=8584,fd=6))                                       
tcp    LISTEN   0        128                     *:10259                *:*      users:(("kube-scheduler",pid=8584,fd=7))   
                                    
[root@k8s-master01 ~]# curl -s http://127.0.0.1:10251/metrics |head
# HELP apiserver_audit_event_total [ALPHA] Counter of audit events generated and sent to the audit backend.
# TYPE apiserver_audit_event_total counter
apiserver_audit_event_total 0
# HELP apiserver_audit_requests_rejected_total [ALPHA] Counter of apiserver requests rejected due to an error in audit logging backend.
# TYPE apiserver_audit_requests_rejected_total counter
apiserver_audit_requests_rejected_total 0
# HELP apiserver_client_certificate_expiration_seconds [ALPHA] Distribution of the remaining lifetime on the certificate used to authenticate a request.
# TYPE apiserver_client_certificate_expiration_seconds histogram
apiserver_client_certificate_expiration_seconds_bucket{le="0"} 0
apiserver_client_certificate_expiration_seconds_bucket{le="1800"} 0

6.04.07 測試 kube-scheduler 集群的高可用

  • 停掉一個或兩個節點的 kube-scheduler 服務,觀察其它節點的日誌,看是否獲取了 leader 權限。
  • 查看當前的 leader
[root@k8s-master02 ~]# kubectl get endpoints kube-scheduler --namespace=kube-system -o yaml

master集群已部署完畢!

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

澳洲野火肆虐動物缺糧 政府空投糧食救援

摘錄自2020年1月13日公視報導

澳洲最近野火燎原,專家估計至少有8億隻野生動物被燒死或受到影響,澳洲當局星期一宣布將投入5000萬澳幣,約合10億新台幣,作為搶救野生動物與復原棲地的經費,而在新南威爾斯則已經空投兩噸的蔬菜糧食,讓當地瀕危的刷尾袋鼠等物種果腹救命。

從直升機上將一箱箱紅蘿蔔和蕃薯空投到地面,攝影機隨後捕捉到小型瀕危的刷尾岩袋鼠現身,抱著救命的糧食啃了起來。為了搶救倖存的野生動物,相關單位已經在國家公園內空投兩噸的蔬菜糧食。

澳洲當局同時在星期一宣布將砸下5000萬澳幣,約合10億台幣,作為救助野生動物的經費,澳洲環境部長表示,其中一半將用於野生動物的醫療照顧與安置,並致力復原被野火燒毀的棲地;另一半將用來搶救面臨生存危機的野生動物。

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

【其他文章推薦】

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

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

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

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

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

匈牙利農場出現H5N8禽流感 將撲殺5萬隻火雞

摘錄自2020年1月13日中央廣播電台報導

匈牙利國家食品安全局(NEBIH)今(13日)宣布,匈牙利西北部一座大型火雞場檢測出雞隻感染高致病性H5N8禽流感病毒株。

匈牙利國家食品安全局表示,這座大型火雞場內的所有火雞,估計超過5萬隻必須加以撲殺,並採取其他預防措施,以遏阻禽流感病毒擴散。

國家食安局表示,匈牙利將實施生禽與禽肉的運輸限制,以阻止禽流感病毒散播,並指H5N8禽流感病毒至今在歐洲並未出現人類感染的風險。

匈牙利北部的鄰邦斯洛伐克(Slovakia)10日也通報西部一處家庭養雞場(backyard poultry)檢測出H5N8禽流感病毒株,同時也是將近三年來,斯洛伐克首度檢測出家禽感染H5N8禽流感病毒株。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

海洋暖化再創新高 能量如36億枚原子彈爆炸

摘錄自2020年1月14日星島日報、公共電視報導

13日發表的一項最新研究報告指出,世界各大海洋的暖化程度十分嚴重,2019年的海水溫度又再創下有紀錄以來的新高水平。科學家形容目前的情況,好比每一秒投放五枚廣島原子彈在海中。

由14位科學家組成的國際團隊合作進行研究,分析上世紀50年代到近年的據數,計算海洋水面至2,000米深的溫度。他們在科學期刊《大氣科學進展》內發表報告,指全球各地海洋暖化的速度愈來愈快。報告指出,在1955至1986年間,海洋暖化的步伐保持穩定,但在過去數十年加速。在1987至2019年間,海洋暖化程度是對上數十年的四倍半。

2019年的海洋溫度,比1981至2010年間的平均溫度高出攝氏0.075度。過去25年海洋吸收的熱力,相等於36億次廣島原子彈爆炸。

研究也顯示,過去5年同時也是有紀錄以來海洋溫度最高的5年。高溫除了使得海洋生物如珊瑚,魚類和海鳥消失,更會導致海平面上升,造成沿岸洪災,以及產生更多颶風,帶來嚴重災情。

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

【其他文章推薦】

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

消費不要黑地球 DO Black卡:全球第一張以碳足跡為額度的信用卡

環境資訊中心記者 吳宜靜報導

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

水都威尼斯運河乾涸 「貢多拉」無法航行

摘錄自2020年1月14日星島日報報導

意大利水都威尼斯兩個月前經歷嚴重洪患,當時大部分市區淹沒在水中。但威尼斯近日卻遭逢低潮,當地運河幾乎乾涸,令河道上的船隻無法航行。

遭逢極端低潮的威尼斯水位大幅下降,河道上的小船「貢多拉」擱淺在岸邊,城內運河看起來更像是充滿泥濘的溝渠,這也讓大多數市民面臨交通問題。澳洲廣播公司(ABC)報道,威尼斯水位一度降到海平面以下45厘米。當局稱,受低水位影響最大的是聖保羅(San Polo)和聖十字(Santa Croce)區的河道。氣象專家預測,未來幾天水位暫不會回升。威尼斯運河在2015、16和18年也出現過乾涸。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

澳洲山火持續多月 NASA:煙塵快將環繞地球一周

摘錄自2020年1月14日星島日報報導

美國太空總署(NASA)指出,澳洲山火造成的煙霧快將環繞地球一周。

NASA指出,元旦前後煙霧已越過南美,令南美國家天空變得朦朧,也嚴重影響紐西蘭。澳洲最近山火非常大,產生「異常多」的火積雲,火積雲使煙霧飛入平流層,其中有煙霧最長錄得17.7公里,煙霧就可飄到四方八面,影響全球大氣環境,部份已抵達智利。NASA稱,大量煙塵有機會形成火積雲,再產生閃電及引發新一輪大火。

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!