如何使用24行JavaScript代碼實現Redux

作者:Yazeed Bzadough
譯者:小維FE
原文:freecodecamp

為了保證文章的可讀性,本文採用意譯而非直譯。

90%的規約,10%的庫。
Redux是迄今為止創建的最重要的JavaScript庫之一,靈感來源於以前的藝術比如和,Redux通過引入一個包含三個簡單要點的可伸縮體繫結構,使得JavaScript函數式編程成為可能。如果你是初次接觸Redux,可以考慮先閱讀。

1. Redux大多是規約

考慮如下這個使用了Redux架構的簡單的計數器應用。如果你想跳過的話可以直接查看。

1.1 State存儲在一棵樹中

該應用程序的狀態看起來如下:

const initialState = { count: 0 };

1.2 Action聲明狀態更改

根據Redux規約,我們不直接修改(突變)狀態。

// 在Redux應用中不要做如下操作
state.count = 1;

相反,我們創建在應用中用戶可能用到的所有行為。

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

1.3 Reducer解釋行為並更新狀態

在最後一個架構部分我們叫做Reduer,其作為一個純函數,它基於以前的狀態和行為返回狀態的新副本。

  • 如果increment被觸發,則增加state.count
  • 如果decrement被觸發,則減少state.count
const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

1.4 目前為止還沒有Redux

你注意到了嗎?到目前為止我們甚至還沒有接觸到Redux庫,我們僅僅只是創建了一些對象和函數,這就是為什麼我稱其為”大多是規約”,90%的Redux應用其實並不需要Redux。

2. 開始實現Redux

要使用這種架構,我們必須要將它放入到一個store當中,我們將僅僅實現一個函數:createStore。使用方式如下:

import { createStore } from 'redux'

const store = createStore(countReducer);

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch(actions.increment);
// logs { count: 1 }

store.dispatch(actions.increment);
// logs { count: 2 }

store.dispatch(actions.decrement);
// logs { count: 1 }

下面這是我們的初始化樣板代碼,我們需要一個監聽器列表listeners和reducer提供的初始化狀態。

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
}

無論何時某人訂閱了我們的store,那麼他將會被添加到listeners數組中。這是非常重要的,因為每次當某人在派發(dispatch)一個動作(action)的時候,所有的listeners都需要在此次事件循環中被通知到。調用yourReducer函數並傳入一個undefined和一個空對象將會返回一個initialState,這個值也就是我們在調用store.getState()時的返回值。既然說到這裏了,我們就來創建這個方法。

2.1 store.getState()

這個函數用於從store中返回最新的狀態,當用戶每次點擊一個按鈕的時候我們都需要最新的狀態來更新我們的視圖。

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
    
    return {
        getState: () => currentState
    };
}

2.2 store.dispatch()

這個函數使用一個action作為其入參,並且將這個actioncurrentState反饋給yourReducer來獲取一個新的狀態,並且dispatch方法還會通知到每一個訂閱了當前store的監聽者。

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    }
  };
};

2.3 store.subscribe(listener)

這個方法使得你在當store接收到一個action的時候能夠被通知到,可以在這裏調用store.getState()來獲取最新的狀態並更新UI。

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

同時subscribe函數返回了另一個函數unsubscribe,這個函數允許你當不再對store的更新感興趣的時候能夠取消訂閱。

3. 整理代碼

現在我們添加按鈕的邏輯,來看看最後的源代碼:

// 簡化版createStore函數
const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

// Redux的架構組成部分
const initialState = { count: 0 };

const actions = {
  increment: { type: 'INCREMENT' },
  decrement: { type: 'DECREMENT' }
};

const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

const store = createStore(countReducer);

// DOM元素
const incrementButton = document.querySelector('.increment');
const decrementButton = document.querySelector('.decrement');

// 給按鈕添加點擊事件
incrementButton.addEventListener('click', () => {
  store.dispatch(actions.increment);
});

decrementButton.addEventListener('click', () => {
  store.dispatch(actions.decrement);
});

// 初始化UI視圖
const counterDisplay = document.querySelector('h1');
counterDisplay.innerHTML = parseInt(initialState.count);

// 派發動作的時候跟新UI
store.subscribe(() => {
  const state = store.getState();

  counterDisplay.innerHTML = parseInt(state.count);
});

我們再次看看最後的視圖效果:

原文:

4. 交流

本篇主要簡單了解下Redux的三個架構組成部分以及如何實現一個簡化版的Redux,對Redux能有進一步的了解,希望能和大家相互討論技術,一起交流學習。

文章已同步更新至,若覺文章尚可,歡迎前往star!

你的一個點贊,值得讓我付出更多的努力!

逆境中成長,只有不斷地學習,才能成為更好的自己,與君共勉!

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

【其他文章推薦】

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

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

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

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

每天3分鐘操作系統修鍊秘籍(13):兩個緩衝空間Kernel Buffer和IO Buffer

兩個緩衝空間:kernel buffer和io buffer

先看一張圖,稍後將圍繞這張圖展開描述。圖中的fd table、open file table以及兩個inode table都可以不用理解,只需要知道它們體現出來的文件描述符和磁盤文件之間的對應關係:文件描述符fd(例如圖中的fd=3)是對應磁盤上文件的。

在Linux下,我們經常會在IO操作時不可避免的涉及到文件描述符,因為Linux下的所有IO操作都是通過文件描述符來完成的。但是,文件描述符是一個非常底層的概念,通過它操作的數據,都是二進制數據,所以通過文件描述符完成IO的模式通常也稱為裸IO(Raw IO)。而且,直接通過底層的文件描述符進行編程會比較麻煩,因為是二進制數據,它缺少很多功能,比如無法指定編碼,無法指定換行符(換行符有多種:\n、\n\r、\r)等等。注意fd是用戶空間的,它僅僅是一個數值而已,並不是想象中感覺比較底層就在內核空間。

所以,現代高級語言(比如C、Python、Java、Golang)都提供了比文件描述符更高一層次的標準IO庫,比如C的標準IO庫是stdio,Python的標準IO庫是IO模塊,等等。使用這些標準IO庫中的函數進行IO操作時,都會使用比文件描述符更高一層次的對象,例如C中稱為IO流(io stream),其它面向對象的語言中一般稱為IO對象,為了方便說明,這裏統稱為IO對象。上圖中的F就是文件對象。

標準IO庫可以看作是文件描述符的更高層次的封裝,提供了比文件描述符操作IO更多的功能。例如,可以在IO對象上指定編碼、指定換行符,此外還在用戶空間提供了一個標準IO庫的緩衝空間,通常可稱為stdio bufferIO buffer,而這些功能在文件描述符上都是沒有的。另外,標準IO庫既然是高層封裝,當然也會提供用戶不使用這些功能(比如不使用IO Buffer),而是直接使用文件描述符,那麼這時候的文件對象就相當於是文件描述符了,這時候的IO操作模式也就是裸IO模式。

所有從硬件讀取或寫入到硬件的數據,默認都會經過操作系統維護的這個Kernel Buffer。正如上圖中描述的是讀數據過程。

例如,cat進程想要讀取a.log文件,cat進程是用戶空間進程,它自身沒有權限打開文件以及讀文件數據,它只能通過系統調用的方式陷入內核,請求操作系統幫助讀取數據,操作系統讀取數據後會將數據放入到page cache(剛才已說明,對於普通文件維護的Kernel buffer稱為page cache或buffer cache)。然後還要將內核空間page cache中的數據拷貝到用戶空間的IO Buffer緩衝空間(因為cat程序的源代碼中使用了標準IO庫stdio),然後cat進程從自己的IO Buffer中讀取數據。這就是整個讀數據的過程。

需要注意的是,雖然這兩段緩衝空間都在內存中,但仍然有拷貝操作,因為內核的內存空間和用戶進程的虛擬內存空間是隔離的,用戶空間進程沒有權限訪問到內核空間的內存,但是內核具有最高權限,允許訪問任何內存地址。換句話說,在將Kernel Buffer的數據拷貝到IO Buffer空間的過程中,需要陷入到內核,OS需要掌控CPU。

此外,Linux也提供了所謂的直接IO模式,只需使用一個稱為O_DIRECT的標記即可,這時會繞過Kernel Buffer,直接將硬件數據拷貝到用戶空間。雖然看上去直接IO少了一個層次的參與感覺性能會更優秀,但實際上並非如此,操作系統為內核緩衝空間做了非常多的優化,使得並不會因此而降低性能。最典型且常見的一個優化是預讀功能,它表示在讀數據時,會比所請求要讀取的數據量多讀一點放入到Kernel Buffer,這樣在下次讀取接下來的一段數據時可以直接從Kernel Buffer中取數據,而無需再和硬件IO交互。所以,使用直接IO模式的場景是非常少的,一般只會在自帶了完整緩衝模型的大型軟件(比如數據庫系統)上可能會使用直接IO模式。

上面所描述的都是讀操作,關於寫操作,這裏不再多花篇幅去描述,整體過程和讀是類似的,都會經過IO Buffer和Kernel Buffer,只是其中一些細節有所不同,如果感興趣,可以閱讀《Linux/Unix系統編程手冊》的第13章。

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

【其他文章推薦】

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

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

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

Magicodes.Pay,打造開箱即用的統一支付庫,已提供ABP模塊封裝

Magicodes.Pay,打造開箱即用的統一支付庫,已提供ABP模塊封裝

簡介

Magicodes.Pay,是心萊科技團隊提供的統一支付庫,相關庫均使用.NET標準庫編寫,支持.NET Framework以及.NET Core。目前已提供Abp模塊的封裝,支持開箱即用。

Nuget

新的包

 

 

已棄用的包,不再更新

 

 

主要功能

Magicodes.Pay,是心萊科技團隊提供的統一支付庫,相關庫均使用.NET標準庫編寫,支持.NET Framework以及.NET Core。目前已提供Abp模塊的封裝,支持開箱即用。目前支持以下支付方式和功能:

  • 支付寶支付

    • APP支付

    • Wap支付

  • 支付寶國際支付

    • 支持分賬

  • 微信支付

    • 小程序支付

    • APP支付

    • 訂單查詢

    • 企業付款(提現)

    • 退款申請

    • 普通紅包

  • 通聯支付

    • 小程序支付

  • 統一支付回調處理

  • 支持日誌函數注入(不依賴支付庫)

  • 支持支付配置函數注入,以便於支持自定義配置獲取邏輯,以應用於不同的場景(比如從配置文件、用戶設置獲取配置,或者多租戶支持)

  • 針對ABP提供模塊封裝,添加模塊依賴即可立即使用。主要包括:

    • 支付渠道註冊(IPaymentRegister)

    • 支付回調邏輯處理(IPaymentCallbackAction)

    • 統一支付服務實現(IToPayService)

    • 統一支付服務封裝(見IPayAppService)

    • 支付管理器封裝(IPaymentManager),包含:

    • 交易日誌封裝,自動記錄客戶端信息以及自動異常處理和記錄

    • 僅需編寫一次回調邏輯,即可支持多個支付渠道

    • 業務參數支持更大長度(500)

開始使用

如果使用Abp相關模塊,則使用起來比較簡單,具體您可以參考相關單元測試的編寫。主要有以下步驟:

  1. 引用對應的Abp支付的Nuget包 如果僅需某個支付,僅需引用該支付的包。下面以通聯支付為例,我們需要在工程中引用此包:

  2. 添加模塊依賴 在對應工程的Abp的模塊(AbpModule)中,添加對“AbpAllinpayModule”的依賴,如:

 [DependsOn(typeof(AbpAllinpayModule))]
  1. 在DbContext中添加名為“TransactionLogs”的DbSet 整個支付過程中(無論是支付成功還是出現異常),均會記錄交易日誌。交易日誌會記錄交易過程中的一些信息,比如客戶端信息、交易參數、自定義參數以及異常信息。因此我們需要針對EF添加對TransactionLog的支持。需要在DbContext中添加的完整代碼如下所示:

public DbSet<TransactionLog> TransactionLogs { get; set; }
  1. 註冊回調邏輯 我們需要實現“IPaymentCallbackAction”接口來編寫自定義的回調邏輯。如以下示例所示:

public class TestPaymentCallbackAction : IPaymentCallbackAction
    {
        /// <summary>
        /// 業務Key
        /// </summary>
        public string Key { get; set; } = "繳費支付";

        /// <summary>
        /// 執行回調
        /// </summary>
        /// <returns></returns>
        public async Task Process(IUnitOfWorkManager unitOfWork, TransactionLog transactionLog)
        {
            var data = transactionLog.CustomData.FromJsonString<JObject>();
            //業務處理

            await Task.FromResult(0);
        }
    }

注意Key不要重複。

  1. 向容器中註冊回調邏輯

我們可以將回調邏輯寫在一個公共的程序集,然後使用以下代碼進行註冊:

 IocManager.IocContainer.Register(
                //註冊自定義支付回調邏輯
                Classes.FromAssembly(typeof(ApplicationCoreModule).GetAssembly())
                    .BasedOn<IPaymentCallbackAction>()
                    .LifestyleTransient()
                    .Configure(component => component.Named(component.Implementation.FullName))
                    .WithServiceFromInterface()
            );

除了上面的方式,我們還可以通過注入IPaymentManager對象,通過其RegisterCallbackAction方法來註冊自定義的回調邏輯。

  1. 發起支付

通過容器獲得IPayAppService,然後調用Pay方法即可。也可以自行封裝:

public async Task<object> Payment(PaymentInput input)
        {
            return await _payAppService.Pay(new PayInputBase()
            {
                Body = $"{input.Name} {input.ChargeProjectName}",
                CustomData = input.ToJsonString(),
                Key = "繳費支付",
                OpenId = input.OpenId,
                Subject = input.ChargeProjectName,
                TotalAmount = input.Amount,
                PayChannel = input.PayChannel
            });
        }

通過IPayAppService統一支付有如下好處:

  • 統一支付(無論支付寶還是微信各種端的支付,均可統一)

  • 自動記錄交易日誌以及進行相關邏輯處理

  • 自定義數據依賴交易日誌進行存儲,而不依賴支付渠道,因此支持無業務參數的支付渠道,也支持存儲更多自定義數據

非ABP集成

請參考Abp相關模塊的封裝或者歷史代碼。

官方訂閱號

關注“麥扣聊技術”訂閱號免費獲取:

  • 最新文章、教程、文檔

  • 視頻教程

  • 基礎版免費授權

  • 模板

  • 解決方案

  • 編程心得和理念

官方博客/文檔站

其他開源庫地址

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

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

詳解Spring Security的formLogin登錄認證模式

一、formLogin的應用場景

在本專欄之前的文章中,已經給大家介紹過Spring Security的HttpBasic模式,該模式比較簡單,只是進行了通過攜帶Http的Header進行簡單的登錄驗證,而且沒有定製的登錄頁面,所以使用場景比較窄。
對於一個完整的應用系統,與登錄驗證相關的頁面都是高度定製化的,非常美觀而且提供多種登錄方式。這就需要Spring Security支持我們自己定製登錄頁面,也就是本文給大家介紹的formLogin模式登錄認證模式。

準備工作

  • 新建一個Spring Boot 的web應用,引入Spring Security Starter。
  • 準備一個login.html登錄頁面,頁面內容非常簡單,一個from表單、用戶名和密碼輸入框,一個提交按鈕
  • 準備一個首頁index.html,在登錄成功之後需要進入index.html首頁
  • 首頁可以看到syslog(日誌管理)、sysuer(用戶管理)、biz1(業務一)、biz2(業務二)四個頁面超文本鏈接選項。通過controller控制層跳轉頁面,並在對應頁面寫一些標誌性文字即可,不需寫具體業務。

需求

  • 我們希望biz1(業務一)、biz2(業務二)普通的操作用戶user就可以訪問
  • 我們希望管理員可以訪問包括syslog(日誌管理)和sysuser(用戶管理)在內的所有資源

以上就是本文介紹formLogin模式需要進行的準備工作及需求,下面我們就來實現其中的核心的登錄驗證邏輯,準備工作非常簡單請自行實現。(新建spring boot應用,登錄頁面、首頁、四個業務頁面都寫成非常簡單的html即可,不用寫實際業務和樣式。)

二、說明

formLogin模式的三要素:

  • 登錄驗證邏輯
  • 資源訪問控制規則,如:資源權限、角色權限
  • 用戶信息

一般來說,使用權限認證框架的的業務系統登錄驗證邏輯是固定的,而資源訪問控制規則和用戶信息是從數據庫或其他存儲介質靈活加載的。但本文所有的用戶、資源、權限信息都是代碼配置寫死的,旨在為大家介紹formLogin認證模式,如何從數據庫加載權限認證相關信息我還會結合RBAC權限模型再寫文章的。

三、實現formLogin模式基礎配置

首先,我們要繼承WebSecurityConfigurerAdapter ,重寫configure(HttpSecurity http) 方法,該方法用來配置登錄驗證邏輯。請注意看下文代碼中的註釋信息。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() //禁用跨站csrf攻擊防禦,後面的章節會專門講解
            .formLogin()
                .loginPage("/login.html")//用戶未登錄時,訪問任何資源都轉跳到該路徑,即登錄頁面
                .loginProcessingUrl("/login")//登錄表單form中action的地址,也就是處理認證請求的路徑
                .usernameParameter("uname")///登錄表單form中用戶名輸入框input的name名,不修改的話默認是username
                .passwordParameter("pword")//form中密碼輸入框input的name名,不修改的話默認是password
                .defaultSuccessUrl("/index")//登錄認證成功后默認轉跳的路徑
                .and()
            .authorizeRequests()
                .antMatchers("/login.html","/login").permitAll()//不需要通過登錄驗證就可以被訪問的資源路徑
                .antMatchers("/biz1").hasAnyAuthority("biz1")  //前面是資源的訪問路徑、後面是資源的名稱或者叫資源ID
                .antMatchers("/biz2").hasAnyAuthority("biz2")
                .antMatchers("/syslog").hasAnyAuthority("syslog")
                .antMatchers("/sysuser").hasAnyAuthority("sysuser")
                .anyRequest().authenticated();
    }
    
}

上面的代碼分為兩部分:

  • 第一部分是formLogin配置段,用於配置登錄驗證邏輯相關的信息。如:登錄頁面、登錄成功頁面、登錄請求處理路徑等。
  • 第二部分是authorizeRequests配置端,用於配置資源的訪問權限。如:開發登錄頁面的permitAll開放訪問,“/biz1”(業務一頁面資源)需要有資源ID為”biz1″的用戶才可以訪問。

這時候,我們通過瀏覽器訪問,隨便測試一個沒有訪問權限的資源,都會跳轉到login.html頁面。

四、實現資源訪問限制的需求

在上文中,我們配置了登錄驗證及資源訪問的權限規則,我們還沒有具體的用戶,下面我們就來配置具體的用戶。重寫WebSecurityConfigurerAdapter的 configure(AuthenticationManagerBuilder auth)方法

public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .withUser("user").password(passwordEncoder().encode("123456")).authorities("biz1","biz2")
        .and()
        .passwordEncoder(passwordEncoder());//配置BCrypt加密
}

@Bean
public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}
  • inMemoryAuthentication指的是在內存裏面存儲用戶的身份認證和授權信息。
  • withUser("user")用戶名是user
  • password(passwordEncoder().encode("123456"))密碼是加密之後的123456
  • authorities("biz1","biz2")指的是user用戶擁有資源ID為biz1(業務一)和biz2(業務二)資源的權限

這樣,我們就實現了文首提出的普通用戶只能訪問biz1(業務一)和biz2(業務二)資源的需求。那麼管理員用戶可以訪問所有的資源的配置方式,你會不會呢?同樣的配方、同樣的方式、自己可以嘗試一下哦!

五、靜態資源訪問

在我們的實際開發中,登錄頁面login.html和控制層Controller登錄驗證’/login’都必須無條件的開放。除此之外,一些靜態資源如css、js文件通常也都不需要驗證權限,我們需要將它們的訪問權限也開放出來。下面就是實現的方法:重寫WebSecurityConfigurerAdapter類的configure(WebSecurity web) 方法


   @Override
    public void configure(WebSecurity web) {
        //將項目中靜態資源路徑開放出來
        web.ignoring().antMatchers("/config/**", "/css/**", "/fonts/**", "/img/**", "/js/**");
    }

期待您的關注

  • 博主最近新寫了一本書:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

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

【其他文章推薦】

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

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

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

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

008.Kubernetes二進制部署Nginx實現高可用

一 Nginx代理實現kube-apiserver高可用

1.1 Nginx實現高可用


基於 nginx 代理的 kube-apiserver 高可用方案。

控制節點的 kube-controller-manager、kube-scheduler 是多實例部署,所以只要有一個實例正常,就可以保證高可用;

集群內的 Pod 使用 K8S 服務域名 kubernetes 訪問 kube-apiserver, kube-dns 會自動解析出多個 kube-apiserver 節點的 IP,所以也是高可用的;

在每個節點起一個 nginx 進程,後端對接多個 apiserver 實例,nginx 對它們做健康檢查和負載均衡;

kubelet、kube-proxy、controller-manager、scheduler 通過本地的 nginx(監聽 127.0.0.1)訪問 kube-apiserver,從而實現 kube-apiserver 的高可用;

從而基於 nginx 4 層透明代理功能實現 K8S 節點( master 節點和 worker 節點)高可用訪問 kube-apiserver 。

1.2 下載編譯Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# wget http://nginx.org/download/nginx-1.15.3.tar.gz
  3 [root@k8smaster01 work]# tar -xzvf nginx-1.15.3.tar.gz
  4 [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3/
  5 [root@k8smaster01 nginx-1.15.3]# mkdir nginx-prefix
  6 [root@k8smaster01 nginx-1.15.3]# ./configure --with-stream --without-http --prefix=$(pwd)/nginx-prefix --without-http_uwsgi_module --without-http_scgi_module --without-http_fastcgi_module
  7 [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3/
  8 [root@k8smaster01 nginx-1.15.3]# make && make install



解釋:

  • –with-stream:開啟 4 層透明轉發(TCP Proxy)功能;
  • –without-xxx:關閉所有其他功能,這樣生成的動態鏈接二進製程序依賴最小。
  • [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3/
  • [root@k8smaster01 nginx-1.15.3]# ./nginx-prefix/sbin/nginx -v

1.3 驗證編譯后的Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work/nginx-1.15.3
  2 [root@k8smaster01 nginx-1.15.3]# ./nginx-prefix/sbin/nginx -v
  3 nginx version: nginx/1.15.3
  4 [root@k8smaster01 nginx-1.15.3]# ldd ./nginx-prefix/sbin/nginx	#查看 nginx 動態鏈接的庫
  5         linux-vdso.so.1 =>  (0x00007ffdda980000)
  6         libdl.so.2 => /lib64/libdl.so.2 (0x00007feb37300000)
  7         libpthread.so.0 => /lib64/libpthread.so.0 (0x00007feb370e4000)
  8         libc.so.6 => /lib64/libc.so.6 (0x00007feb36d17000)
  9         /lib64/ld-linux-x86-64.so.2 (0x00007feb37504000)



提示:由於只開啟了 4 層透明轉發功能,所以除了依賴 libc 等操作系統核心 lib 庫外,沒有對其它 lib 的依賴(如 libz、libssl 等),以便達到精簡編譯的目的。

1.4 安裝和部署Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     mkdir -p /opt/k8s/kube-nginx/{conf,logs,sbin}
  7   done						#創建Nginx目錄
  8 [root@k8smaster01 ~]# cd /opt/k8s/work
  9 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 10 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
 11   do
 12     echo ">>> ${master_ip}"
 13     scp /opt/k8s/work/nginx-1.15.3/nginx-prefix/sbin/nginx  root@${master_ip}:/opt/k8s/kube-nginx/sbin/kube-nginx
 14     ssh root@${master_ip} "chmod a+x /opt/k8s/kube-nginx/sbin/*"
 15     ssh root@${master_ip} "mkdir -p /opt/k8s/kube-nginx/{conf,logs,sbin}"
 16   done						#分發Nginx二進制


1.5 配置Nginx 四層透明轉發

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-nginx.conf <<EOF
  3 worker_processes 1;
  4 
  5 events {
  6     worker_connections  1024;
  7 }
  8 
  9 stream {
 10     upstream backend {
 11         hash $remote_addr consistent;
 12         server 172.24.8.71:6443        max_fails=3 fail_timeout=30s;
 13         server 172.24.8.72:6443        max_fails=3 fail_timeout=30s;
 14         server 172.24.8.73:6443        max_fails=3 fail_timeout=30s;
 15     }
 16 
 17     server {
 18         listen 127.0.0.1:8443;
 19         proxy_connect_timeout 1s;
 20         proxy_pass backend;
 21     }
 22 }
 23 EOF
 24 [root@k8smaster01 ~]# cd /opt/k8s/work
 25 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 26 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
 27   do
 28     echo ">>> ${master_ip}"
 29     scp kube-nginx.conf  root@${master_ip}:/opt/k8s/kube-nginx/conf/kube-nginx.conf
 30   done						#分發Nginx四層透明代理配置文件


1.6 配置Nginx system

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-nginx.service <<EOF
  3 [Unit]
  4 Description=kube-apiserver nginx proxy
  5 After=network.target
  6 After=network-online.target
  7 Wants=network-online.target
  8 
  9 [Service]
 10 Type=forking
 11 ExecStartPre=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx -t
 12 ExecStart=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx
 13 ExecReload=/opt/k8s/kube-nginx/sbin/kube-nginx -c /opt/k8s/kube-nginx/conf/kube-nginx.conf -p /opt/k8s/kube-nginx -s reload
 14 PrivateTmp=true
 15 Restart=always
 16 RestartSec=5
 17 StartLimitInterval=0
 18 LimitNOFILE=65536
 19 
 20 [Install]
 21 WantedBy=multi-user.target
 22 EOF


1.7 分發Nginx systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     scp kube-nginx.service  root@${master_ip}:/etc/systemd/system/
  7   done


二 啟動並驗證

2.1 啟動Nginx

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "systemctl daemon-reload && systemctl enable kube-nginx && systemctl restart kube-nginx"
  7   done


2.2 檢查Nginx服務

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "systemctl status kube-nginx |grep 'Active:'"
  7   done


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

【其他文章推薦】

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

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

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

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

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  ?

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

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

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

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