1.使用xshell登錄到阿里云服務(wù)器。安裝nginx(本文安裝到/etc下)
創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供麻陽網(wǎng)站建設(shè)、麻陽做網(wǎng)站、麻陽網(wǎng)站設(shè)計(jì)、麻陽網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、麻陽企業(yè)網(wǎng)站模板建站服務(wù),10年麻陽做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
[plain]?view plain?copy
cd?/etc??
apt-get?update??
apt-get?install?nginx??
2.首先先配置nginx,然后再根據(jù)配置文件做下一步操作
打開/etc/nginx/nginx.conf文件
[plain]?view plain?copy
vim?/etc/nginx/nginx.conf??
在nginx.conf中配置如下:
[plain]?view plain?copy
user?www-data;??
worker_processes?auto;??
pid?/run/nginx.pid;??
events?{??
worker_connections?768;??
#?multi_accept?on;??
}??
http?{??
##??
#?Basic?Settings??
##??
tcp_nodelay?on;??
keepalive_timeout?65;??
types_hash_max_size?2048;??
#?server_tokens?off;??
#?server_names_hash_bucket_size?64;??
#?server_name_in_redirect?off;??
include?/etc/nginx/mime.types;??
default_type?application/octet-stream;??
##??
#?SSL?Settings??
##??
ssl_protocols?TLSv1?TLSv1.1?TLSv1.2;?#?Dropping?SSLv3,?ref:?POODLE??
ssl_prefer_server_ciphers?on;??
##??
#?Logging?Settings??
##??
access_log?/var/log/nginx/access.log;??
error_log?/var/log/nginx/error.log;??
##??
#?Gzip?Settings??
##??
gzip?on;??
gzip_disable?"msie6";??
#?gzip_vary?on;??
#?gzip_proxied?any;??
#?gzip_comp_level?6;??
#?gzip_buffers?16?8k;??
#?gzip_http_version?1.1;??
##??
#?Virtual?Host?Configs??
##??
gzip?on;??
gzip_disable?"msie6";??
#?gzip_vary?on;??
#?gzip_proxied?any;??
#?gzip_comp_level?6;??
#?gzip_buffers?16?8k;??
#?gzip_http_version?1.1;??
#?gzip_types?text/plain?text/css?application/json?application/javascript?text/xml?application/xml?application/xml+rss?text/javascript;??
##??
#?Virtual?Host?Configs??
##??
include?/etc/nginx/conf.d/*.conf;??
include?/etc/nginx/sites-enabled/*;??
#以下為我們添加的內(nèi)容??
server?{???????????????
listen?80;??
server_name?your-ipaddress;??
root?/home/my-project/;??
index?index.html;??
location?/datas?{??
rewrite?^.+datas/?(.*)$?/$1?break;??
include?uwsgi_params;??
proxy_pass?;??
}??
}??
}??
接下來就根據(jù)配置文件進(jìn)行下一步工作。配置文件中的server_name后面是阿里云服務(wù)器的ip地址
3.配置文件中的listen是nginx監(jiān)聽的端口號(hào),所以需要在阿里云服務(wù)器上為80端口添加安全組規(guī)則
在本地的瀏覽器登錄阿里云服務(wù)器-進(jìn)入控制臺(tái)-點(diǎn)擊安全組-點(diǎn)擊配置規(guī)則-點(diǎn)擊添加安全組規(guī)則,之后配置如下(注:入方向和出方向都要配置)
4.配置文件中的root和index那兩行表示我們把項(xiàng)目文件夾放在/home/my-project下
例如有兩個(gè)項(xiàng)目文件夾分別為test1,test2,里面都有index.html。則目錄結(jié)構(gòu)如下
/home
|--my-project
|--test1
|--index.html
|--test2
|--index.html
則在瀏覽器輸入
服務(wù)器便會(huì)在/home/my-project中找到test1下的index.html執(zhí)行;
如果在瀏覽器中輸入
服務(wù)器便會(huì)在/home/my-project中找到test2下的index.html執(zhí)行;
這樣便可以在服務(wù)器下放多個(gè)項(xiàng)目文件夾。
5.所以我們也需要在本地項(xiàng)目的config/index.js里的build下進(jìn)行修改,如果要把項(xiàng)目放到test1下,則
[javascript]?view plain?copy
assetsPublicPath:?'/test1/',??
如果用到了vue-router,則修改/router/index.js
[javascript]?view plain?copy
export?default?new?Router({??
base:?'/test1/',???//添加這行??
linkActiveClass:?'active',??
routes??
});??
6.nginx配置文件中的location則是針對跨域處理,表示把對/datas的請求轉(zhuǎn)發(fā)給,本文中這個(gè)下就是需要的數(shù)據(jù),例如,在本地項(xiàng)目文件中ajax請求數(shù)據(jù)的地方如下
[javascript]?view plain?copy
const?url?=?'/datas/seller';??
this.$http.get(url).then((response)?=?{??
.....??
});??
7.修改后在本地命令行下運(yùn)行:cnpm run build 生成dist文件。把dist文件里的index.html和static文件上傳到服務(wù)器的/home/my-project/test1下,目錄結(jié)構(gòu)如下
/home
|--my-project
|--test1
|--index.html
|--static
8.啟動(dòng)nginx
[plain]?view plain?copy
service?nginx?start??
9.至此項(xiàng)目部署成功,在瀏覽器下輸入:? ? 即可
在Web開發(fā)領(lǐng)域,相信大家對于Cookie和Session都很熟悉,Cookie和Session都是會(huì)話保持技術(shù)的解決方案。隨著技術(shù)的發(fā)展,Token機(jī)制出現(xiàn)在我們面前,不過很多開發(fā)者對于Token和Cookie、Session的區(qū)別及使用場景分辨不清。
Cookie和Session的用途
要知道我們訪問網(wǎng)站都是通過HTTP協(xié)議或HTTPS協(xié)議來完成的, HTTP協(xié)議它本身是無狀態(tài)的協(xié)議 (即:服務(wù)器無法分辨哪些請求是來源于同個(gè)客戶)。而業(yè)務(wù)層面會(huì)涉及到客戶端與服務(wù)器端的交互(同網(wǎng)站下多個(gè)頁面間能共享數(shù)據(jù)),此時(shí)服務(wù)器端必須要保持會(huì)話狀態(tài),這樣才能進(jìn)行用戶身份的鑒別。
由于HTTP無狀態(tài)的特性,如果要實(shí)話客戶端和服務(wù)器端的會(huì)話保持,那就需要其它機(jī)制來實(shí)現(xiàn),于是Cookie和Session應(yīng)運(yùn)而生。
通常情況下, Session和Cookie是搭配在一起使用的 。
Token是什么
上面說到的Session和Cookie機(jī)制來保持會(huì)話,會(huì)存在一個(gè)問題:客戶端瀏覽器只要保存自己的SessionID即可,而 服務(wù)器卻要保存所有用戶的Session信息,這對于服務(wù)器來說開銷較大,而且不利用服務(wù)器的擴(kuò)展 (比如服務(wù)器集群時(shí),Session如何同步存儲(chǔ)就是個(gè)問題)!
于是有人思考,如果把Session信息讓客戶端來保管而且無法偽造不就可以解決這個(gè)問題了?進(jìn)而有了Token機(jī)制。
Token俗稱為“令牌” ,它的構(gòu)成是:
Token機(jī)制下的認(rèn)證流程
Token機(jī)制其實(shí)和Cookie機(jī)制極其相似 ,主要有以下流程:
1、用戶登錄進(jìn)行身份認(rèn)證,認(rèn)證成功后服務(wù)器端生成Token返回給客戶端;
2、客戶端接收到Token后保存在客戶端(可保存在Cookie、LocalStorage、SessionStorage中);
3、客戶端再次請求服務(wù)器端時(shí),將Token作為請求頭放入Headers中;
4、服務(wù)器端接收請求頭中的Token,將用戶參數(shù)按照既定規(guī)則再進(jìn)行一次簽名,兩次簽名若一致則認(rèn)為成功,反之?dāng)?shù)據(jù)存在篡改請求失敗。
(生成簽名示例圖)
(驗(yàn)證簽名示例圖)
Token與Cookie+Session的區(qū)別
Cookie其實(shí)也充當(dāng)?shù)氖橇钆谱饔茫恰坝袪顟B(tài)”的; 而Token令牌是無狀態(tài)的,更利于分布式部署。
session和cookie
在講Token之前,先簡單說說什么是session和cookie。
Token
但是這里會(huì)有個(gè)問題, 服務(wù)器要保存所有用戶的session信息,開銷會(huì)很大,如果在分布式的架構(gòu)下,就需要考慮session共享的問題,需要做額外的設(shè)計(jì)和開發(fā) ,例如把session中的信息保存到Redis中進(jìn)行共享;所以因?yàn)檫@個(gè)原因,有人考慮這些信息是否可以讓客戶端保存,可以保存到任何地方,并且保證其安全性,于是就有了Token。
Token是服務(wù)端生成的一串字符串,可以看做客戶端進(jìn)行請求的一個(gè)令牌。
基于Token的認(rèn)證流程
整體的流程是這樣的:
總結(jié) 希望我的回答,能夠幫助到你!我將持續(xù)分享Java開發(fā)、架構(gòu)設(shè)計(jì)、程序員職業(yè)發(fā)展等方面的見解,希望能得到你的關(guān)注。
Token顧名思義就是令牌、憑證、鑰匙 。只有這把鑰匙,你才能打開門。token一般都是服務(wù)端生成,比如一個(gè)web系統(tǒng),用戶登錄的時(shí)候,服務(wù)端校驗(yàn)用戶名密碼通過以后,會(huì)生成一個(gè)token,同時(shí)會(huì)生成refreshToken和一個(gè)過期時(shí)間。然后將refreshToken和token返回給客戶端??蛻舳藭?huì)將token保存下來。后續(xù)所有的請求都會(huì)攜帶這個(gè)token。服務(wù)端會(huì)判斷當(dāng)前token是否存在已經(jīng)是否過期。如果token不存在或者過期就會(huì)拒絕本次請求。如果token過期怎么辦,就用refreshToken刷新時(shí)間。當(dāng)然這里可能還有別的方案。比如只生成token,每次請求的時(shí)候都刷新過期時(shí)間。如果長時(shí)間沒有刷新過期時(shí)間,那token就會(huì)過期。
session就是回話,這是服務(wù)端的一種操作。當(dāng)你第一次訪問一個(gè)web網(wǎng)站的時(shí)候,服務(wù)端會(huì)生成一個(gè)session,并有一個(gè)sessionid和他對應(yīng)。這個(gè)session是存儲(chǔ)到內(nèi)存中的,你可以向這個(gè)session中寫入信息,比如當(dāng)前登錄用戶的信息。sessionid會(huì)被返回到客戶端,客戶端一般采用cookie來保存。當(dāng)然這個(gè)cookie不用人為寫入。用tomcat容器來舉個(gè)例子。 當(dāng)后端調(diào)用HttpServletRequest對象的getSession的方法的時(shí)候,tomcat內(nèi)部會(huì)生成一個(gè)jsessonid(tomcat sessionid的叫法)。這個(gè)jsessonid會(huì)隨本次請求返回給客戶端。響應(yīng)頭信息
這個(gè)jessionid就會(huì)寫到cookie中。之后jessionid就會(huì)通過cookie傳遞到服務(wù)端。
這里我們就會(huì)很清楚了, session的數(shù)據(jù)是存儲(chǔ)到內(nèi)存中。那問題就來了,如果我們的服務(wù)是分布式部署,有多臺(tái)機(jī)器的話,可能我們第一次登陸的時(shí)候,我們把用戶的信息存儲(chǔ)到了session,但是后面的請求到了B機(jī)器上,那B機(jī)器是獲取不到用戶的session的。另外就是session存儲(chǔ)在內(nèi)存中,那服務(wù)器重啟,session就丟失了,這就是他的弊端?,F(xiàn)在有一些技術(shù),例如session共享、iphash、session持久等也可以解決上述問題 。
cookie是瀏覽器的一種策略。上述講到了sessionid就是存儲(chǔ)在cookie中的。我們知道http協(xié)議是無狀態(tài)的,cookie就是用來解決這個(gè)問題的。cookie中可以用來保存服務(wù)端返回的一些用戶信息的,例如前文提到的token、sessionid。每一次的請求,都會(huì)攜帶這些cookie。服務(wù)端從請求頭中取到cookie中的信息,就可以識(shí)別本次請求的來源,這樣,http是不是就變成有狀態(tài)的了。
這里說幾點(diǎn)cookie注意事項(xiàng)。
1、cookie存放在客戶端,所以是不安全的。人為可以清除
2、cookie有過期時(shí)間設(shè)定。如果不設(shè)置過期時(shí)間,說明這個(gè)cookie就是當(dāng)前瀏覽器的會(huì)話時(shí)間,瀏覽器關(guān)了,cookie 就存在了。如果有過期時(shí)間,cookie就會(huì)存儲(chǔ)到硬盤上,瀏覽器關(guān)閉不影響cookie。下次打開瀏覽器,cookie還存在
3、cookie有大小的限制,4KB。
這個(gè)問題,網(wǎng)上有很多的答案,相信都看過了,估計(jì)也沒有看明白。所以我就不去網(wǎng)上復(fù)制了,用自己的話,盡量說通俗,說重點(diǎn)。
cookie和session實(shí)際上是同一套認(rèn)證流程,相輔相成。session保存在服務(wù)器,cookie保存在客戶端。最常見的做法就是客戶端的cookie僅僅保存一個(gè)sessionID,這個(gè)sessionID是一個(gè)毫無規(guī)則的隨機(jī)數(shù),由服務(wù)器在客戶端登錄通過后隨機(jī)生產(chǎn)的。往后,客戶端每次訪問該網(wǎng)站都要帶上這個(gè)由sessionID組成的cookie。服務(wù)器收到請求,首先拿到客戶端的sessionID,然后從服務(wù)器內(nèi)存中查詢它所代表的客戶端(用戶名,用戶組,有哪些權(quán)限等)。
與token相比,這里的重點(diǎn)是,服務(wù)器必須保存sessionID以及該ID所代表的客戶端信息。這些內(nèi)容可以保存在內(nèi)存,也可以保存到數(shù)據(jù)庫(通常是內(nèi)存數(shù)據(jù)庫)。
而token則可以服務(wù)器完全不用保存任何登錄信息。
token的流程是這樣的??蛻舳说卿浲ㄟ^后,服務(wù)器生成一堆客戶端身份信息,包括用戶名、用戶組、有那些權(quán)限、過期時(shí)間等等。另外再對這些信息進(jìn)行簽名。之后把身份信息和簽名作為一個(gè)整體傳給客戶端。這個(gè)整體就叫做token。之后,客戶端負(fù)責(zé)保存該token,而服務(wù)器不再保存??蛻舳嗣看卧L問該網(wǎng)站都要帶上這個(gè)token。服務(wù)器收到請求后,把它分割成身份信息和簽名,然后驗(yàn)證簽名,若驗(yàn)證成功,就直接使用身份信息(用戶名、用戶組、有哪些權(quán)限等等)。
可以看出,相對于cookie/session機(jī)制,token機(jī)制中,服務(wù)器根本不需要保存用戶的身份信息(用戶名、用戶組、權(quán)限等等)。這樣就減輕了服務(wù)器的負(fù)擔(dān)。
我們舉個(gè)例來說,假如目前有一千萬個(gè)用戶登錄了,在訪問不同的網(wǎng)頁。如果用cookie/session,則服務(wù)器內(nèi)存(或內(nèi)存數(shù)據(jù)庫)中要同時(shí)記錄1千萬個(gè)用戶的信息。每次客戶端訪問一個(gè)頁面,服務(wù)器都要從內(nèi)存中查詢出他的登錄信息。而如果用token,則服務(wù)器內(nèi)存中不記錄用戶登錄信息。它只需要在收到請求后,直接使用客戶端發(fā)過來的登錄身份信息。
可以這么說, cookie/session是服務(wù)器說客戶端是誰,客戶端才是誰。而token是客戶端說我(客戶端)是誰,我就是誰 。當(dāng)然了,token是有簽名機(jī)制的。要是客戶端偽造身份,簽名通不過。這個(gè)簽名算法很簡單,就是將客戶端的身份信息加上一個(gè)只有服務(wù)器知道的鹽值(不能泄露),然后進(jìn)行md5散列算法(這里只是簡化,方便理解,實(shí)際細(xì)節(jié)要稍復(fù)雜一些)。
cookie/session在單服務(wù)器,單域名時(shí)比較簡單,否則的話,就要考慮如何將客戶端的session保存或同步到多個(gè)服務(wù)器。還要考慮一旦宕機(jī),內(nèi)存中的這些信息是否會(huì)丟失。token因?yàn)榉?wù)器不保存用戶身份,就不存在這個(gè)問題。這是token的優(yōu)點(diǎn)。
token因?yàn)榉?wù)器不保存用戶身份信息,一切都依賴當(dāng)初那個(gè)簽名。所以存在被盜用的風(fēng)險(xiǎn)。也就是說一旦盜用,服務(wù)器可能毫無辦法,因?yàn)樗徽J(rèn)簽名算法。而session機(jī)制,服務(wù)器看誰不爽,可以隨時(shí)把他踢出(從內(nèi)存中刪掉)。正是因?yàn)槿绱?,token高度依賴過期時(shí)間。過期時(shí)間不能太長。過期短,可以減少被盜用的風(fēng)險(xiǎn)。
除了上面所說的,我個(gè)人認(rèn)為,如果開發(fā)的系統(tǒng)足夠小,傾向于使用cookie/session。如果系統(tǒng)同時(shí)登錄用戶多,集群服務(wù)器多,有單點(diǎn)登錄需求,則傾向于使用token。
萬維網(wǎng)的發(fā)展 歷史
Token, 令牌,代表執(zhí)行某些操作的權(quán)利的對象。
token主要用于鑒權(quán)使用,主要有以下幾類:
cookie主要是網(wǎng)站用于在瀏覽器臨時(shí)存放的數(shù)據(jù),包括瀏覽器緩存數(shù)據(jù)以及服務(wù)器設(shè)定的一些數(shù)據(jù),主要存放在瀏覽器端。
session主要用于保存會(huì)話數(shù)據(jù),一般存儲(chǔ)在服務(wù)器端,同時(shí)每一條session對用一個(gè)sessionID,sessionID是存放在瀏覽器的cookie中。
傳統(tǒng)上的會(huì)話登陸和鑒權(quán)主要用session加cookie實(shí)現(xiàn),隨著分布式系統(tǒng)的快速演進(jìn),尤其是微服務(wù)的應(yīng)用,token+cookie的授權(quán)訪問機(jī)制得到親睞,通常在用戶登錄后,服務(wù)器生成訪問令牌(Access token),瀏覽器存儲(chǔ)cookie中,在每次請求資源時(shí)都會(huì)在請求頭中帶上token,用于服務(wù)器授權(quán)訪問使用。
Token和session都是web網(wǎng)站的會(huì)話保持、認(rèn)證的解決方案;
既然都一樣為什么還有token的說法。
從token產(chǎn)生的背景說起
1.移動(dòng)端應(yīng)用使得服務(wù)器端Session失效
2.分布式系統(tǒng)中Session無法共享
所以說session對于以上兩種情況無效了,所以有了Token的說法
那么什么是token,token長什么樣子?
先給大家一個(gè)直觀的感受
token:PC-3066014fa0b10792e4a762-23-20170531133947-4f6496
說白了token保存就是用戶的信息(不能保存密碼等敏感信息)
token的組成:
客戶端標(biāo)識(shí)-USERCODE-USERID-CREATIONDATE-RONDEM[6位]
USERCODE,RONDEM[6位]經(jīng)過MD5加密就變成了以上字符串
token的請求流程
請求流程解析
1.前端用戶發(fā)送登錄信息至認(rèn)證系統(tǒng)
2.驗(yàn)證用戶登錄信息,判斷用戶是否存在
3.如果用戶存在,生成token信息(客戶端標(biāo)識(shí)-USERCODE-USERID-CREATIONDATE-RONDEM[6位]),并存儲(chǔ)在redis中
4.并將該token返回前端,附加至header
驗(yàn)證token
客戶端
將token附加至header
服務(wù)端
最后總結(jié)一下
一般的垂直架構(gòu)項(xiàng)目使用Session沒有任何問題,但是分布式項(xiàng)目或涉及到移動(dòng)端則考慮使用token。
session
session的中文翻譯是“會(huì)話”,當(dāng)用戶打開某個(gè)web應(yīng)用時(shí),便與web服務(wù)器產(chǎn)生一次session。服務(wù)器使用session把用戶的信息臨時(shí)保存在了服務(wù)器上,用戶離開網(wǎng)站后session會(huì)被銷毀。這種用戶信息存儲(chǔ)方式相對cookie來說更安全,可是session有一個(gè)缺陷:如果web服務(wù)器做了負(fù)載均衡,那么下一個(gè)操作請求到了另一臺(tái)服務(wù)器的時(shí)候session會(huì)丟失。
cookie
cookie是保存在本地終端的數(shù)據(jù)。cookie由服務(wù)器生成,發(fā)送給瀏覽器,瀏覽器把cookie以kv形式保存到某個(gè)目錄下的文本文件內(nèi),下一次請求同一網(wǎng)站時(shí)會(huì)把該cookie發(fā)送給服務(wù)器。由于cookie是存在客戶端上的,所以瀏覽器加入了一些限制確保cookie不會(huì)被惡意使用,同時(shí)不會(huì)占據(jù)太多磁盤空間,所以每個(gè)域的cookie數(shù)量是有限的。
cookie的組成有:名稱(key)、值(value)、有效域(domain)、路徑(域的路徑,一般設(shè)置為全局:"")、失效時(shí)間、安全標(biāo)志(指定后,cookie只有在使用SSL連接時(shí)才發(fā)送到服務(wù)器(https))。下面是一個(gè)簡單的js使用cookie的例子:
用戶登錄時(shí)產(chǎn)生cookie:
document.cookie = "id="+result.data['id']+"; path=/";
document.cookie = "name="+result.data['name']+"; path=/";
document.cookie = "avatar="+result.data['avatar']+"; path=/";
使用到cookie時(shí)做如下解析:
var cookie = document.cookie;var cookieArr = cookie.split(";");var user_info = {};for(var i = 0; i cookieArr.length; i++) {
user_info[cookieArr[i].split("=")[0]] = cookieArr[i].split("=")[1];
}
$('#user_name').text(user_info[' name']);
$('#user_avatar').attr("src", user_info[' avatar']);
$('#user_id').val(user_info[' id']);
token
token的意思是“令牌”,是用戶身份的驗(yàn)證方式,最簡單的token組成:uid(用戶唯一的身份標(biāo)識(shí))、time(當(dāng)前時(shí)間的時(shí)間戳)、sign(簽名,由token的前幾位+鹽以哈希算法壓縮成一定長的十六進(jìn)制字符串,可以防止惡意第三方拼接token請求服務(wù)器)。還可以把不變的參數(shù)也放進(jìn)token,避免多次查庫
cookie 和session的區(qū)別
1、cookie數(shù)據(jù)存放在客戶的瀏覽器上,session數(shù)據(jù)放在服務(wù)器上。
2、cookie不是很安全,別人可以分析存放在本地的COOKIE并進(jìn)行COOKIE欺騙
考慮到安全應(yīng)當(dāng)使用session。
3、session會(huì)在一定時(shí)間內(nèi)保存在服務(wù)器上。當(dāng)訪問增多,會(huì)比較占用你服務(wù)器的性能
考慮到減輕服務(wù)器性能方面,應(yīng)當(dāng)使用COOKIE。
4、單個(gè)cookie保存的數(shù)據(jù)不能超過4K,很多瀏覽器都限制一個(gè)站點(diǎn)最多保存20個(gè)cookie。
5、所以個(gè)人建議:
將登陸信息等重要信息存放為SESSION
其他信息如果需要保留,可以放在COOKIE中
token 和session 的區(qū)別
session 和 oauth token并不矛盾,作為身份認(rèn)證 token安全性比session好,因?yàn)槊總€(gè)請求都有簽名還能防止監(jiān)聽以及重放攻擊,而session就必須靠鏈路層來保障通訊安全了。如上所說,如果你需要實(shí)現(xiàn)有狀態(tài)的會(huì)話,仍然可以增加session來在服務(wù)器端保存一些狀態(tài)
App通常用restful api跟server打交道。Rest是stateless的,也就是app不需要像browser那樣用cookie來保存session,因此用session token來標(biāo)示自己就夠了,session/state由api server的邏輯處理。 如果你的后端不是stateless的rest api, 那么你可能需要在app里保存session.可以在app里嵌入webkit,用一個(gè)隱藏的browser來管理cookie session.
Session 是一種HTTP存儲(chǔ)機(jī)制,目的是為無狀態(tài)的HTTP提供的持久機(jī)制。所謂Session 認(rèn)證只是簡單的把User 信息存儲(chǔ)到Session 里,因?yàn)镾ID 的不可預(yù)測性,暫且認(rèn)為是安全的。這是一種認(rèn)證手段。 而Token ,如果指的是OAuth Token 或類似的機(jī)制的話,提供的是 認(rèn)證 和 授權(quán) ,認(rèn)證是針對用戶,授權(quán)是針對App 。其目的是讓 某App有權(quán)利訪問 某用戶 的信息。這里的 Token是唯一的。不可以轉(zhuǎn)移到其它 App上,也不可以轉(zhuǎn)到其它 用戶 上。 轉(zhuǎn)過來說Session 。Session只提供一種簡單的認(rèn)證,即有此 SID,即認(rèn)為有此 User的全部權(quán)利。是需要嚴(yán)格保密的,這個(gè)數(shù)據(jù)應(yīng)該只保存在站方,不應(yīng)該共享給其它網(wǎng)站或者第三方App。 所以簡單來說,如果你的用戶數(shù)據(jù)可能需要和第三方共享,或者允許第三方調(diào)用 API 接口,用 Token 。如果永遠(yuǎn)只是自己的網(wǎng)站,自己的 App,用什么就無所謂了。
token就是令牌,比如你授權(quán)(登錄)一個(gè)程序時(shí),他就是個(gè)依據(jù),判斷你是否已經(jīng)授權(quán)該軟件;cookie就是寫在客戶端的一個(gè)txt文件,里面包括你登錄信息之類的,這樣你下次在登錄某個(gè)網(wǎng)站,就會(huì)自動(dòng)調(diào)用cookie自動(dòng)登錄用戶名;session和cookie差不多,只是session是寫在服務(wù)器端的文件,也需要在客戶端寫入cookie文件,但是文件里是你的瀏覽器編號(hào).Session的狀態(tài)是存儲(chǔ)在服務(wù)器端,客戶端只有session id;而Token的狀態(tài)是存儲(chǔ)在客戶端。
想要全面深入地掌握Token,我們需要先了解這些:Token的概念、身份驗(yàn)證過程、實(shí)現(xiàn)思路、使用場景,以及Cookie、Session、Token的區(qū)別。
內(nèi)容綱要
Token的定義
Token是驗(yàn)證用戶身份的一種方式,簡稱做“令牌”。最簡單的token組成:uid(用戶唯一的身份標(biāo)識(shí))、time(當(dāng)前時(shí)間的時(shí)間戳)、sign(簽名,由token的前幾位+鹽,以哈希算法壓縮成一定長的十六進(jìn)制字符串,可以防止惡意第三方拼接token請求服務(wù)器)。還可以把不變的參數(shù)也放進(jìn)token,避免多次查庫。
Token的身份驗(yàn)證過程
每一次請求都需要Token,Token應(yīng)該在HTTP的頭部發(fā)送,從而保證Http請求無狀態(tài)。我們同樣通過設(shè)置服務(wù)器屬性Access-Control-Allow-Origin:* ,讓服務(wù)器能接受到來自所有域的請求。
需要注意的是,在ACAO頭部標(biāo)明(designating)*時(shí),不得帶有像HTTP認(rèn)證,客戶端SSL證書和cookies的證書。
Token的實(shí)現(xiàn)思路
當(dāng)我們在程序中認(rèn)證了信息,并取得Token之后,我們便能通過這個(gè)Token做許多的事情。我們甚至能基于創(chuàng)建一個(gè)基于權(quán)限的token傳給第三方應(yīng)用程序,這些第三方程序能夠獲取到我們的數(shù)據(jù)(當(dāng)然只有在我們允許的特定的token)。
Token的應(yīng)用場景
Cookie和Session的區(qū)別
綜合以上考量,建議方案:
Session和Cookie取長補(bǔ)短、配合使用,將登陸信息等重要信息存放為Session,其他信息如果需要保留,可以放在Cookie中。
Token 和 Session 的區(qū)別
Session和Token并不矛盾,作為身份認(rèn)證,Token安全性比Session高,因?yàn)門oken發(fā)送的每個(gè)請求都帶有簽名,能防止監(jiān)聽,以及重放攻擊。而session就必須靠鏈路層來保障通訊安全。如上所說,如果需要實(shí)現(xiàn)有狀態(tài)的會(huì)話,可以通過增加session,在服務(wù)器端保存一些狀態(tài)。
App通常用Restful API跟server打交道。Rest是Stateless的,也就是App不需要像Browser那樣用Cookie來保存Session,因此使用Session Token來標(biāo)示就足夠了。Session/state由API Server的邏輯處理。如果后端不是Stateless的rest API,那么可能需要在App里保存Session,可以在App里嵌入webkit,用一個(gè)隱藏的Browser來管理Cookie Session.
Session是一種HTTP存儲(chǔ)機(jī)制,目的是為無狀態(tài)的HTTP提供持久機(jī)制。 所謂的Session認(rèn)證,只是簡單的把User信息存儲(chǔ)到Session里,因?yàn)镾ID的不可預(yù)測性,暫且認(rèn)為是安全的,這是一種認(rèn)證手段。
Session只提供一種簡單的認(rèn)證,即有此SID,以及User的全部權(quán)利。是需要嚴(yán)格保密的,這個(gè)數(shù)據(jù)只在使用站點(diǎn)保存,不可共享給其它網(wǎng)站或者第三方App。所以簡單來說,如果你的用戶數(shù)據(jù)可能需要和第三方共享,或者允許第三方調(diào)用API接口,則使用Token,如果只是自己的網(wǎng)站或App應(yīng)用,使用什么都OK。
Token,指的是OAuth Token或類似的機(jī)制的話,提供的是認(rèn)證和授權(quán) ,認(rèn)證是針對用戶,授權(quán)是針對App。其目的是讓某App有權(quán)利訪問某用戶的信息,這里的Token是唯一的,不可以轉(zhuǎn)移到其它App上,也不可以轉(zhuǎn)到其它用戶上。
Token就是令牌,比如你授權(quán)(登錄)一個(gè)程序時(shí),他就是個(gè)依據(jù),判斷你是否已經(jīng)授權(quán)該軟件。Cookie就是寫在客戶端的一個(gè)txt文件,記錄下用戶的訪問、登錄等信息,下次用戶再登錄某個(gè)網(wǎng)站時(shí),服務(wù)器接收到請求,就會(huì)自動(dòng)調(diào)用Cookie,自動(dòng)登錄用戶名。
Session和Cookie差不多,只是Session是寫在服務(wù)器端的文件,也需要在客戶端寫入Cookie文件,但是文件里是用戶的瀏覽器編號(hào).Session的狀態(tài)是存儲(chǔ)在服務(wù)器端,客戶端只有session id,而Token的狀態(tài)是存儲(chǔ)在客戶端的。
以上,是關(guān)于Token、Session、Cookie的知識(shí)點(diǎn)介紹,更加深入的詳解,感興趣的童鞋,可查看我持續(xù)分享的【BAT架構(gòu)技術(shù)專題合集500+】,回復(fù)【架構(gòu)】,即可領(lǐng)取。
Token是什么?
Token是令牌、憑證、鑰匙,在Web領(lǐng)域中進(jìn)行 身份驗(yàn)證 ,關(guān)鍵點(diǎn)在驗(yàn)證??!,說驗(yàn)證就必須了解一下Web領(lǐng)域的發(fā)展史呢。
2. 隨著人們需求的改變,比如在線購物系統(tǒng),需要登陸的網(wǎng)站等,這時(shí)候需要進(jìn)行 交互 性,服務(wù)器接收到請求的時(shí)候,要根據(jù)你是否登陸,以及判斷你是誰,來給你響應(yīng),這時(shí)候問題就來了,怎么知道每次請求的誰呢,所以就出來了一個(gè) 會(huì)話標(biāo)識(shí)(session id),就是一個(gè)隨機(jī)的字符串 ,每個(gè)人登陸的時(shí)候,服務(wù)器都會(huì)返回一個(gè)會(huì)話標(biāo)識(shí),這是再請求的時(shí)候,只要帶上會(huì)話標(biāo)識(shí),服務(wù)器就知道請求的是誰。
3. 這樣子每個(gè)人只要保存自己的session id就可以啦,服務(wù)器要保存 所有人 的session id?。?!如果有成千上萬的人訪問服務(wù)器,那對服務(wù)器是巨大的開銷,嚴(yán)重限制了服務(wù)器端的擴(kuò)展能力,比如機(jī)器A跟機(jī)器B組成了服務(wù)器集群,那么訪問了機(jī)器A,會(huì)話標(biāo)識(shí)在機(jī)器A上,如果轉(zhuǎn)到機(jī)器B,就不能訪問了,也許會(huì)說,那復(fù)制呢,機(jī)器A搬到機(jī)器B,也有說統(tǒng)一把標(biāo)識(shí)放在一個(gè)機(jī)器上,但是萬一這個(gè)機(jī)器掛了呢,那體驗(yàn)就很差了。
4. 這個(gè)時(shí)候就有人想,用戶自己保存自己的標(biāo)識(shí),就是Token,訪問的時(shí)候帶上這個(gè)Token,這個(gè) Token是用戶id+簽名 ,驗(yàn)證時(shí),服務(wù)器只要相同的算法和服務(wù)器才知道的密鑰進(jìn)行簽名,如果結(jié)果跟Token中的簽名一樣,那就可以證明是登陸過的用戶。
這樣一來,服務(wù)器不保存session id,只要生成Token,訪問時(shí),只要對Token進(jìn)行判斷,Token也是有有效期的,所以也要進(jìn)行refreshToken的。
Token,Cookie,Session三者使用場景的區(qū)別?
Token主要是Web領(lǐng)域的身份認(rèn)證 ,最常見的就是Web API這個(gè)功能:
Cookie 就是餅干, 它是服務(wù)器生產(chǎn),永久保存在瀏覽器的數(shù)據(jù),以kv的形式 ,你可以打開你的瀏覽器(這里以win10 edge為例),點(diǎn)擊上方的三個(gè)點(diǎn)的按鈕,再點(diǎn)擊更多工具,再點(diǎn)擊開發(fā)人員工具,再點(diǎn)擊網(wǎng)絡(luò),此時(shí)內(nèi)容選擇文檔,然后刷新頁面,找Cookie即可。
Session是會(huì)話標(biāo)識(shí),是服務(wù)器用來判斷正在會(huì)話的用戶是誰,服務(wù)器生產(chǎn)的隨機(jī)數(shù) ,保存在服務(wù)器中,用戶端也要進(jìn)行保存,雖然能實(shí)現(xiàn)會(huì)話的共能,但對服務(wù)器的擴(kuò)展能力限制,同時(shí)當(dāng)服務(wù)器是兩臺(tái)機(jī)器組成以上的時(shí)候,會(huì)導(dǎo)致兩臺(tái)機(jī)器以上保存的session同步問題,會(huì)導(dǎo)致用戶體驗(yàn)極差。
Token跟Session最大的區(qū)別就是Token服務(wù)端不用保存,同時(shí)是通過簽名等技術(shù)實(shí)現(xiàn)的,Session因?yàn)槭请S機(jī)數(shù),導(dǎo)致服務(wù)器要進(jìn)行保存。
后端主要是讓服務(wù)器、應(yīng)用、數(shù)據(jù)庫能夠彼此交互,需要考慮如何實(shí)現(xiàn)功能、數(shù)據(jù)的存取、平臺(tái)的穩(wěn)定性與性能等。常用的腳本語言有php、 java 、 python、C、C++等,以java為例主要用到的技術(shù)包括但不限于Struts、spring、springmvc 、Hibernate、Http協(xié)議、Servlet、Tomcat服務(wù)器等
第一,Servlet技術(shù)。Servlet技術(shù)是Java后端的重要技術(shù)之一,作為Java Web開發(fā)的核心組件,Servlet承擔(dān)了Web MVC結(jié)構(gòu)中的核心作用(功能導(dǎo)航)。傳統(tǒng)的Model2結(jié)構(gòu)(Servlet+JavaBean+JSP)雖然在目前已經(jīng)很少使用了,但是Web開發(fā)的基本結(jié)構(gòu)依然沒有改變。Servlet技術(shù)的應(yīng)用涉及到Web容器、會(huì)話(HttpSession)、安全、同步、Web應(yīng)用部署等相關(guān)內(nèi)容。
第二,Java操作數(shù)據(jù)庫。后端開發(fā)免不了與數(shù)據(jù)庫打交道,所以掌握J(rèn)ava的數(shù)據(jù)庫操作是一個(gè)基本要求。Java操作數(shù)據(jù)庫涉及到的內(nèi)容有JDBC、JNDI、RMI、DAO等內(nèi)容,其中使用RMI+JDBC是構(gòu)建java數(shù)據(jù)庫開發(fā)的一個(gè)常見的解決方案,而JNDI則是對各種資源的定義。
第三,Spring框架。Spring+SpringMVC+MyBatis是目前一個(gè)比較常見的后端開發(fā)方案,Spring的原理就是構(gòu)建了一個(gè)“業(yè)務(wù)組件容器”,SpringMVC則是Web MVC的一個(gè)具體實(shí)現(xiàn)框架,而MyBatis則是一個(gè)基于DAO的實(shí)現(xiàn)框架。從性能的角度來說,Spring是EJB的輕量級(jí)解決方案,得到了廣大Java程序員的歡迎。如果有Servlet以及數(shù)據(jù)庫操作的基礎(chǔ),那么學(xué)習(xí)這幾個(gè)框架的使用是一件非常輕松的過程。雖然基于Spring的編程比較方便,但是Spring也有缺點(diǎn),比如配置文件過于繁瑣。
第四,結(jié)合hadoop構(gòu)建Java的分布式開發(fā)。Java的分布式開發(fā)是提高Java后端處理能力的重要內(nèi)容,RMI是Java分布式開發(fā)比較常見的解決方案,學(xué)習(xí)起來也比較簡單
我們知道容器的特點(diǎn)是快速創(chuàng)建、快速銷毀,Kubernetes Pod和容器一樣只具有臨時(shí)的生命周期,一個(gè)Pod隨時(shí)有可能被終止或者漂移,隨著集群的狀態(tài)變化而變化,一旦Pod變化,則該P(yáng)od提供的服務(wù)也就無法訪問,如果直接訪問Pod則無法實(shí)現(xiàn)服務(wù)的連續(xù)性和高可用性,因此顯然不能使用Pod地址作為服務(wù)暴露端口。
解決這個(gè)問題的辦法和傳統(tǒng)數(shù)據(jù)中心解決無狀態(tài)服務(wù)高可用的思路完全一樣,通過負(fù)載均衡和VIP實(shí)現(xiàn)后端真實(shí)服務(wù)的自動(dòng)轉(zhuǎn)發(fā)、故障轉(zhuǎn)移。
這個(gè)負(fù)載均衡在Kubernetes中稱為Service,VIP即Service ClusterIP,因此可以認(rèn)為Kubernetes的Service就是一個(gè)四層負(fù)載均衡,Kubernetes對應(yīng)的還有七層負(fù)載均衡Ingress,本文僅介紹Kubernetes Service。
這個(gè)Service就是由kube-proxy實(shí)現(xiàn)的,ClusterIP不會(huì)因?yàn)镻odz狀態(tài)改變而變,需要注意的是VIP即ClusterIP是個(gè)假的IP,這個(gè)IP在整個(gè)集群中根本不存在,當(dāng)然也就無法通過IP協(xié)議棧無法路由,底層underlay設(shè)備更無法感知這個(gè)IP的存在,因此ClusterIP只能是單主機(jī)(Host Only)作用域可見,這個(gè)IP在其他節(jié)點(diǎn)以及集群外均無法訪問。
Kubernetes為了實(shí)現(xiàn)在集群所有的節(jié)點(diǎn)都能夠訪問Service,kube-proxy默認(rèn)會(huì)在所有的Node節(jié)點(diǎn)都創(chuàng)建這個(gè)VIP并且實(shí)現(xiàn)負(fù)載,所以在部署Kubernetes后發(fā)現(xiàn)kube-proxy是一個(gè)DaemonSet。
而Service負(fù)載之所以能夠在Node節(jié)點(diǎn)上實(shí)現(xiàn)是因?yàn)闊o論Kubernetes使用哪個(gè)網(wǎng)絡(luò)模型,均需要保證滿足如下三個(gè)條件:
至少第2點(diǎn)是必須滿足的,有了如上幾個(gè)假設(shè),Kubernetes Service才能在Node上實(shí)現(xiàn),否則Node不通Pod IP也就實(shí)現(xiàn)不了了。
有人說既然kube-proxy是四層負(fù)載均衡,那kube-proxy應(yīng)該可以使用haproxy、nginx等作為負(fù)載后端???
事實(shí)上確實(shí)沒有問題,不過唯一需要考慮的就是性能問題,如上這些負(fù)載均衡功能都強(qiáng)大,但畢竟還是基于用戶態(tài)轉(zhuǎn)發(fā)或者反向代理實(shí)現(xiàn)的,性能必然不如在內(nèi)核態(tài)直接轉(zhuǎn)發(fā)處理好。
因此kube-proxy默認(rèn)會(huì)優(yōu)先選擇基于內(nèi)核態(tài)的負(fù)載作為后端實(shí)現(xiàn)機(jī)制,目前kube-proxy默認(rèn)是通過iptables實(shí)現(xiàn)負(fù)載的,在此之前還有一種稱為userspace模式,其實(shí)也是基于iptables實(shí)現(xiàn),可以認(rèn)為當(dāng)前的iptables模式是對之前userspace模式的優(yōu)化。
本節(jié)接下來將詳細(xì)介紹kube-proxy iptables模式的實(shí)現(xiàn)原理。
首先創(chuàng)建了一個(gè)ClusterIP類型的Service:
其中ClusterIP為10.106.224.41,我們可以驗(yàn)證這個(gè)IP在本地是不存在的:
所以 不要嘗試去ping ClusterIP,它不可能通的 。
此時(shí)在Node節(jié)點(diǎn)192.168.193.172上訪問該Service服務(wù),首先流量到達(dá)的是OUTPUT鏈,這里我們只關(guān)心nat表的OUTPUT鏈:
該鏈跳轉(zhuǎn)到 KUBE-SERVICES 子鏈中:
我們發(fā)現(xiàn)與之相關(guān)的有兩條規(guī)則:
其中 KUBE-SVC-RPP7DHNHMGOIIFDC 子鏈規(guī)則如下:
這幾條規(guī)則看起來復(fù)雜,其實(shí)實(shí)現(xiàn)的功能很簡單:
我們查看其中一個(gè)子鏈 KUBE-SEP-FTIQ6MSD3LWO5HZX 規(guī)則:
可見這條規(guī)則的目的是做了一次DNAT,DNAT目標(biāo)為其中一個(gè)Endpoint,即Pod服務(wù)。
由此可見子鏈 KUBE-SVC-RPP7DHNHMGOIIFDC 的功能就是按照概率均等的原則DNAT到其中一個(gè)Endpoint IP,即Pod IP,假設(shè)為10.244.1.2,
此時(shí)相當(dāng)于:
接著來到POSTROUTING鏈:
這兩條規(guī)則只做一件事就是只要標(biāo)記了 0x4000/0x4000 的包就一律做MASQUERADE(SNAT),由于10.244.1.2默認(rèn)是從flannel.1轉(zhuǎn)發(fā)出去的,因此會(huì)把源IP改為flannel.1的IP 10.244.0.0 。
剩下的就是常規(guī)的走Vxlan隧道轉(zhuǎn)發(fā)流程了,這里不再贅述,感興趣的可以參考我之前的文章 淺聊幾種主流Docker網(wǎng)絡(luò)的實(shí)現(xiàn)原理 。
接下來研究下NodePort過程,首先創(chuàng)建如下Service:
其中Service的NodePort端口為30419。
假設(shè)有一個(gè)外部IP 192.168.193.197,通過 192.168.193.172:30419 訪問服務(wù)。
首先到達(dá)PREROUTING鏈:
PREROUTING的規(guī)則非常簡單,凡是發(fā)給自己的包,則交給子鏈 KUBE-NODEPORTS 處理。注意前面省略了判斷ClusterIP的部分規(guī)則。
KUBE-NODEPORTS 規(guī)則如下:
這個(gè)規(guī)則首先給包打上標(biāo)記 0x4000/0x4000 ,然后交給子鏈 KUBE-SVC-RPP7DHNHMGOIIFDC 處理, KUBE-SVC-RPP7DHNHMGOIIFDC 剛剛已經(jīng)見面過了,其功能就是按照概率均等的原則DNAT到其中一個(gè)Endpoint IP,即Pod IP,假設(shè)為10.244.1.2。
此時(shí)發(fā)現(xiàn)10.244.1.2不是自己的IP,于是經(jīng)過路由判斷目標(biāo)為10.244.1.2需要從flannel.1發(fā)出去。
接著到了 FORWARD 鏈,
FORWARD表在這里只是判斷下,只允許打了標(biāo)記 0x4000/0x4000 的包才允許轉(zhuǎn)發(fā)。
最后來到 POSTROUTING 鏈,這里和ClusterIP就完全一樣了,在 KUBE-POSTROUTING 中做一次 MASQUERADE (SNAT),最后結(jié)果:
我們發(fā)現(xiàn)基于iptables模式的kube-proxy ClusterIP和NodePort都是基于iptables規(guī)則實(shí)現(xiàn)的,我們至少發(fā)現(xiàn)存在如下幾個(gè)問題:
本文接下來將介紹kube-proxy的ipvs實(shí)現(xiàn),由于本人之前也是對ipvs很陌生,沒有用過,專門學(xué)習(xí)了下ipvs,因此在第二章簡易介紹了下ipvs,如果已經(jīng)很熟悉ipvs了,可以直接跳過,這一章和Kubernetes幾乎沒有任何關(guān)系。
另外由于本人對ipvs也是初學(xué),水平有限,難免出錯(cuò),歡迎指正!
我們接觸比較多的是應(yīng)用層負(fù)載均衡,比如haproxy、nginx、F5等,這些負(fù)載均衡工作在用戶態(tài),因此會(huì)有對應(yīng)的進(jìn)程和監(jiān)聽socket,一般能同時(shí)支持4層負(fù)載和7層負(fù)載,使用起來也比較方便。
LVS是國內(nèi)章文嵩博士開發(fā)并貢獻(xiàn)給社區(qū)的( 章文嵩博士和他背后的負(fù)載均衡帝國 ),主要由ipvs和ipvsadm組成,ipvs是工作在內(nèi)核態(tài)的4層負(fù)載均衡,和iptables一樣都是基于內(nèi)核底層netfilter實(shí)現(xiàn),netfilter主要通過各個(gè)鏈的鉤子實(shí)現(xiàn)包處理和轉(zhuǎn)發(fā)。ipvsadm和ipvs的關(guān)系,就好比netfilter和iptables的關(guān)系,它運(yùn)行在用戶態(tài),提供簡單的CLI接口進(jìn)行ipvs配置。
由于ipvs工作在內(nèi)核態(tài),直接基于內(nèi)核處理包轉(zhuǎn)發(fā),所以最大的特點(diǎn)就是性能非常好。又由于它工作在4層,因此不會(huì)處理應(yīng)用層數(shù)據(jù),經(jīng)常有人問ipvs能不能做SSL證書卸載、或者修改HTTP頭部數(shù)據(jù),顯然這些都不可能做的。
我們知道應(yīng)用層負(fù)載均衡大多數(shù)都是基于反向代理實(shí)現(xiàn)負(fù)載的,工作在應(yīng)用層,當(dāng)用戶的包到達(dá)負(fù)載均衡監(jiān)聽器listening后,基于一定的算法從后端服務(wù)列表中選擇其中一個(gè)后端服務(wù)進(jìn)行轉(zhuǎn)發(fā)。當(dāng)然中間可能還會(huì)有一些額外操作,最常見的如SSL證書卸載。
而ipvs工作在內(nèi)核態(tài),只處理四層協(xié)議,因此只能基于路由或者NAT進(jìn)行數(shù)據(jù)轉(zhuǎn)發(fā),可以把ipvs當(dāng)作一個(gè)特殊的路由器網(wǎng)關(guān),這個(gè)網(wǎng)關(guān)可以根據(jù)一定的算法自動(dòng)選擇下一跳,或者把ipvs當(dāng)作一個(gè)多重DNAT,按照一定的算法把ip包的目標(biāo)地址DNAT到其中真實(shí)服務(wù)的目標(biāo)IP。針對如上兩種情況分別對應(yīng)ipvs的兩種模式–網(wǎng)關(guān)模式和NAT模式,另外ipip模式則是對網(wǎng)關(guān)模式的擴(kuò)展,本文下面會(huì)針對這幾種模式的實(shí)現(xiàn)原理進(jìn)行詳細(xì)介紹。
ipvsadm命令行用法和iptables命令行用法非常相似,畢竟是兄弟,比如 -L 列舉, -A 添加, -D 刪除。
但是其實(shí)ipvsadm相對iptables命令簡直太簡單了,因?yàn)闆]有像iptables那樣存在各種table,table嵌套各種鏈,鏈里串著一堆規(guī)則,ipvsadm就只有兩個(gè)核心實(shí)體,分別為service和server,service就是一個(gè)負(fù)載均衡實(shí)例,而server就是后端member,ipvs術(shù)語中叫做real server,簡稱RS。
如下命令創(chuàng)建一個(gè)service實(shí)例 172.17.0.1:32016 , -t 指定監(jiān)聽的為 TCP 端口, -s 指定算法為輪詢算法rr(Round Robin),ipvs支持簡單輪詢(rr)、加權(quán)輪詢(wrr)、最少連接(lc)、源地址或者目標(biāo)地址散列(sh、dh)等10種調(diào)度算法。
然后把10.244.1.2:8080、10.244.1.3:8080、10.244.3.2:8080添加到service后端member中。
其中 -t 指定service實(shí)例, -r 指定server地址, -w 指定權(quán)值, -m 即前面說的轉(zhuǎn)發(fā)模式,其中 -m 表示為 masquerading ,即NAT模式, -g 為 gatewaying ,即直連路由模式, -i 為 ipip ,ji即IPIP隧道模式。
與iptables-save、iptables-restore對應(yīng)的工具ipvs也有ipvsadm-save、ipvsadm-restore。
NAT模式由字面意思理解就是通過NAT實(shí)現(xiàn)的,但究竟是如何NAT轉(zhuǎn)發(fā)的,我們通過實(shí)驗(yàn)環(huán)境驗(yàn)證下。
現(xiàn)環(huán)境中LB節(jié)點(diǎn)IP為192.168.193.197,三個(gè)RS節(jié)點(diǎn)如下:
為了模擬LB節(jié)點(diǎn)IP和RS不在同一個(gè)網(wǎng)絡(luò)的情況,在LB節(jié)點(diǎn)中添加一個(gè)虛擬IP地址:
創(chuàng)建負(fù)載均衡Service并把RS添加到Service中:
這里需要注意的是,和應(yīng)用層負(fù)載均衡如haproxy、nginx不一樣的是,haproxy、nginx進(jìn)程是運(yùn)行在用戶態(tài),因此會(huì)創(chuàng)建socket,本地會(huì)監(jiān)聽端口,而 ipvs的負(fù)載是直接運(yùn)行在內(nèi)核態(tài)的,因此不會(huì)出現(xiàn)監(jiān)聽端口 :
可見并沒有監(jiān)聽10.222.0.1:8080 Socket 。
Client節(jié)點(diǎn)IP為192.168.193.226,為了和LB節(jié)點(diǎn)的虛擬IP 10.222.0.1通,我們手動(dòng)添加靜態(tài)路由如下:
此時(shí)Client節(jié)點(diǎn)能夠ping通LB節(jié)點(diǎn)VIP:
可見Client節(jié)點(diǎn)到VIP的鏈路沒有問題,那是否能夠訪問我們的Service呢?
我們驗(yàn)證下:
非常意外的結(jié)果是并不通。
在RS節(jié)點(diǎn)抓包如下:
我們發(fā)現(xiàn)數(shù)據(jù)包的源IP為Client IP,目標(biāo)IP為RS IP,換句話說,LB節(jié)點(diǎn)IPVS只做了DNAT,把目標(biāo)IP改成RS IP了,而沒有修改源IP。此時(shí)雖然RS和Client在同一個(gè)子網(wǎng),鏈路連通性沒有問題,但是由于Client節(jié)點(diǎn)發(fā)出去的包的目標(biāo)IP和收到的包源IP不一致,因此會(huì)被直接丟棄,相當(dāng)于給張三發(fā)信,李四回的信,顯然不受信任。
既然IPVS沒有給我們做SNAT,那自然想到的是我們手動(dòng)做SNAT,在LB節(jié)點(diǎn)添加如下iptables規(guī)則:
再次檢查Service是否可以訪問:
服務(wù)依然不通。并且在LB節(jié)點(diǎn)的iptables日志為空:
也就是說,ipvs的包根本不會(huì)經(jīng)過iptables nat表POSTROUTING鏈?
那mangle表呢?我們打開LOG查看下:
此時(shí)查看日志如下:
我們發(fā)現(xiàn)在mangle表中可以看到DNAT后的包。
只是可惜mangle表的POSTROUTING并不支持NAT功能:
對比Kubernetes配置發(fā)現(xiàn)需要設(shè)置如下系統(tǒng)參數(shù):
再次驗(yàn)證:
終于通了,查看RS抓包:
如期望,修改了源IP為LB IP。
原來需要配置 net.ipv4.vs.conntrack=1 參數(shù),這個(gè)問題折騰了一個(gè)晚上,不得不說目前ipvs的文檔都太老了。
前面是通過手動(dòng)iptables實(shí)現(xiàn)SNAT的,性能可能會(huì)有損耗,于是如下開源項(xiàng)目通過修改lvs直接做SNAT:
除了SNAT的辦法,是否還有其他辦法呢?想想我們最初的問題,Client節(jié)點(diǎn)發(fā)出去的包的目標(biāo)IP和收到的包源IP不一致導(dǎo)致包被丟棄,那解決問題的辦法就是把包重新引到LB節(jié)點(diǎn)上,只需要在所有的RS節(jié)點(diǎn)增加如下路由即可:
此時(shí)我們再次檢查我們的Service是否可連接:
結(jié)果沒有問題。
不過我們是通過手動(dòng)添加Client IP到所有RS的明細(xì)路由實(shí)現(xiàn)的,如果Client不固定,這種方案仍然不太可行,所以通常做法是干脆把所有RS默認(rèn)路由指向LB節(jié)點(diǎn),即把LB節(jié)點(diǎn)當(dāng)作所有RS的默認(rèn)網(wǎng)關(guān)。
由此可知,用戶通過LB地址訪問服務(wù),LB節(jié)點(diǎn)IPVS會(huì)把用戶的目標(biāo)IP由LB IP改為RS IP,源IP不變,包不經(jīng)過iptables的OUTPUT直接到達(dá)POSTROUTING轉(zhuǎn)發(fā)出去,包回來的時(shí)候也必須先到LB節(jié)點(diǎn),LB節(jié)點(diǎn)把目標(biāo)IP再改成用戶的源IP,最后轉(zhuǎn)發(fā)給用戶。
顯然這種模式來回都需要經(jīng)過LB節(jié)點(diǎn),因此又稱為雙臂模式。
網(wǎng)關(guān)模式(Gatewaying)又稱為直連路由模式(Direct Routing)、透傳模式, 所謂透傳即LB節(jié)點(diǎn)不會(huì)修改數(shù)據(jù)包的源IP、端口以及目標(biāo)IP、端口 ,LB節(jié)點(diǎn)做的僅僅是路由轉(zhuǎn)發(fā)出去,可以把LB節(jié)點(diǎn)看作一個(gè)特殊的路由器網(wǎng)關(guān),而RS節(jié)點(diǎn)則是網(wǎng)關(guān)的下一跳,這就相當(dāng)于對于同一個(gè)目標(biāo)地址,會(huì)有多個(gè)下一跳,這個(gè)路由器網(wǎng)關(guān)的特殊之處在于能夠根據(jù)一定的算法選擇其中一個(gè)RS作為下一跳,達(dá)到負(fù)載均衡和冗余的效果。
既然是通過直連路由的方式轉(zhuǎn)發(fā),那顯然LB節(jié)點(diǎn)必須與所有的RS節(jié)點(diǎn)在同一個(gè)子網(wǎng),不能跨子網(wǎng),否則路由不可達(dá)。換句話說, 這種模式只支持內(nèi)部負(fù)載均衡(Internal LoadBalancer) 。
另外如前面所述,LB節(jié)點(diǎn)不會(huì)修改源端口和目標(biāo)端口,因此這種模式也無法支持端口映射,換句話說 LB節(jié)點(diǎn)監(jiān)聽的端口和所有RS節(jié)點(diǎn)監(jiān)聽的端口必須一致 。
現(xiàn)在假設(shè)有LB節(jié)點(diǎn)IP為 192.168.193.197 ,有三個(gè)RS節(jié)點(diǎn)如下:
創(chuàng)建負(fù)載均衡Service并把RS添加到Service中:
注意到我們的Service監(jiān)聽的端口30620和RS的端口是一樣的,并且通過 -g 參數(shù)指定為直連路由模式(網(wǎng)關(guān)模式)。
Client節(jié)點(diǎn)IP為192.168.193.226,我們驗(yàn)證Service是否可連接:
我們發(fā)現(xiàn)并不通,在其中一個(gè)RS節(jié)點(diǎn)192.168.193.172上抓包:
正如前面所說,LB是通過路由轉(zhuǎn)發(fā)的,根據(jù)路由的原理,源MAC地址修改為LB的MAC地址,而目標(biāo)MAC地址修改為RS MAC地址,相當(dāng)于RS是LB的下一跳。
并且源IP和目標(biāo)IP都不會(huì)修改。問題就來了,我們Client期望訪問的是RS,但RS收到的目標(biāo)IP卻是LB的IP,發(fā)現(xiàn)這個(gè)目標(biāo)IP并不是自己的IP,因此不會(huì)通過INPUT鏈轉(zhuǎn)發(fā)到用戶空間,這時(shí)要不直接丟棄這個(gè)包,要不根據(jù)路由再次轉(zhuǎn)發(fā)到其他地方,總之兩種情況都不是我們期望的結(jié)果。
那怎么辦呢?為了讓RS接收這個(gè)包,必須得讓RS有這個(gè)目標(biāo)IP才行。于是不妨在lo上添加個(gè)虛擬IP,IP地址偽裝成LB IP 192.168.193.197:
問題又來了,這就相當(dāng)于有兩個(gè)相同的IP,IP重復(fù)了怎么辦?辦法是隱藏這個(gè)虛擬網(wǎng)卡,不讓它回復(fù)ARP,其他主機(jī)的neigh也就不可能知道有這么個(gè)網(wǎng)卡的存在了,參考 Using arp announce/arp ignore to disable ARP 。
此時(shí)再次從客戶端curl:
終于通了。
我們從前面的抓包中知道,源IP為Client IP 192.168.193.226,因此直接回包給Client即可,不可能也不需要再回到LB節(jié)點(diǎn)了,即A-B,B-C,C-A,流量方向是三角形狀的,因此這種模式又稱為三角模式。
我們從原理中不難得出如下結(jié)論:
前面介紹了網(wǎng)關(guān)直連路由模式,要求所有的節(jié)點(diǎn)在同一個(gè)子網(wǎng),而ipip隧道模式則主要解決這種限制,LB節(jié)點(diǎn)IP和RS可以不在同一個(gè)子網(wǎng),此時(shí)需要通過ipip隧道進(jìn)行傳輸。
現(xiàn)在假設(shè)有LB節(jié)點(diǎn)IP為 192.168.193.77/25 ,在該節(jié)點(diǎn)上增加一個(gè)VIP地址:
ip addr add 192.168.193.48/25 dev eth0
有三個(gè)RS節(jié)點(diǎn)如下:
如上三個(gè)RS節(jié)點(diǎn)子網(wǎng)掩碼均為255.255.255.128,即25位子網(wǎng),顯然和VIP 192.168.193.48/25不在同一個(gè)子網(wǎng)。
創(chuàng)建負(fù)載均衡Service并把RS添加到Service中:
注意到我們的Service監(jiān)聽的端口30620和RS的端口是一樣的,并且通過 -i 參數(shù)指定為ipip隧道模式。
在所有的RS節(jié)點(diǎn)上加載ipip模塊以及添加VIP(和直連路由類型):
Client節(jié)點(diǎn)IP為192.168.193.226/25,我們驗(yàn)證Service是否可連接:
Service可訪問,我們在RS節(jié)點(diǎn)上抓包如下:
我們發(fā)現(xiàn)和直連路由一樣,源IP和目標(biāo)IP沒有修改。
所以IPIP模式和網(wǎng)關(guān)(Gatewaying)模式原理基本一樣,唯一不同的是網(wǎng)關(guān)(Gatewaying)模式要求所有的RS節(jié)點(diǎn)和LB節(jié)點(diǎn)在同一個(gè)子網(wǎng),而IPIP模式則可以支持跨子網(wǎng)的情況,為了解決跨子網(wǎng)通信問題,使用了ipip隧道進(jìn)行數(shù)據(jù)傳輸。
ipvs是一個(gè)內(nèi)核態(tài)的四層負(fù)載均衡,支持NAT、Gateway以及IPIP隧道模式,Gateway模式性能最好,但LB和RS不能跨子網(wǎng),IPIP性能次之,通過ipip隧道解決跨網(wǎng)段傳輸問題,因此能夠支持跨子網(wǎng)。而NAT模式?jīng)]有限制,這也是唯一一種支持端口映射的模式。
我們不難猜想,由于Kubernetes Service需要使用端口映射功能,因此kube-proxy必然只能使用ipvs的NAT模式。
使用kubeadm安裝Kubernetes可參考文檔 Cluster Created by Kubeadm ,不過這個(gè)文檔的安裝配置有問題 kubeadm #1182 ,如下官方配置不生效:
需要修改為如下配置:
可以通過如下命令確認(rèn)kube-proxy是否修改為ipvs:
創(chuàng)建一個(gè)ClusterIP類似的Service如下:
ClusterIP 10.96.54.11為我們查看ipvs配置如下:
可見ipvs的LB IP為ClusterIP,算法為rr,RS為Pod的IP。
另外我們發(fā)現(xiàn)使用的模式為NAT模式,這是顯然的,因?yàn)槌薔AT模式支持端口映射,其他兩種均不支持端口映射,所以必須選擇NAT模式。
由前面的理論知識(shí),ipvs的VIP必須在本地存在,我們可以驗(yàn)證:
可見kube-proxy首先會(huì)創(chuàng)建一個(gè)dummy虛擬網(wǎng)卡kube-ipvs0,然后把所有的Service IP添加到kube-ipvs0中。
我們知道基于iptables的Service,ClusterIP是一個(gè)虛擬的IP,因此這個(gè)IP是ping不通的,但ipvs中這個(gè)IP是在每個(gè)節(jié)點(diǎn)上真實(shí)存在的,因此可以ping通:
當(dāng)然由于這個(gè)IP就是配置在本地虛擬網(wǎng)卡上,所以對診斷問題沒有一點(diǎn)用處的。
我們接下來研究下ClusterIP如何傳遞的。
當(dāng)我們通過如下命令連接服務(wù)時(shí):
此時(shí)由于10.96.54.11就在本地,所以會(huì)以這個(gè)IP作為出口地址,即源IP和目標(biāo)IP都是10.96.54.11,此時(shí)相當(dāng)于:
其中xxxx為隨機(jī)端口。
然后經(jīng)過ipvs,ipvs會(huì)從RS ip列中選擇其中一個(gè)Pod ip作為目標(biāo)IP,假設(shè)為10.244.2.2:
我們從iptables LOG可以驗(yàn)證:
我們查看OUTPUT安全組規(guī)則如下:
其中ipsetj集合 KUBE-CLUSTER-IP 保存著所有的ClusterIP以及監(jiān)聽端口。
如上規(guī)則的意思就是除了Pod以外訪問ClusterIP的包都打上 0x4000/0x4000 。
到了POSTROUTING鏈:
如上規(guī)則的意思就是只要匹配mark 0x4000/0x4000 的包都做SNAT,由于10.244.2.2是從flannel.1出去的,因此源ip會(huì)改成flannel.1的ip 10.244.0.0 :
最后通過Vxlan 隧道發(fā)到Pod的Node上,轉(zhuǎn)發(fā)給Pod的veth,回包通過路由到達(dá)源Node節(jié)點(diǎn),源Node節(jié)點(diǎn)通過之前的MASQUERADE再把目標(biāo)IP還原為10.96.54.11。
查看Service如下:
Service kubernetes-bootcamp-v1的NodePort為32016。
現(xiàn)在假設(shè)集群外的一個(gè)IP 192.168.193.197訪問192.168.193.172:32016:
最先到達(dá)PREROUTING鏈:
如上4條規(guī)則看起來復(fù)雜,其實(shí)就做一件事,如果目標(biāo)地址為NodeIP,則把包標(biāo)記 0x4000 , 0x4000 。
我們查看ipvs:
我們發(fā)現(xiàn)和ClusterIP實(shí)現(xiàn)原理非常相似,ipvs Service的VIP為Node IP,端口為NodePort。ipvs會(huì)選擇其中一個(gè)Pod IP作為DNAT目標(biāo),這里假設(shè)為10.244.3.2:
剩下的到了POSTROUTING鏈就和Service ClusterIP完全一樣了,只要匹配 0x4000/0x4000 的包就會(huì)做SNAT。
Kubernetes的ClusterIP和NodePort都是通過ipvs service實(shí)現(xiàn)的,Pod當(dāng)作ipvs service的server,通過NAT MQSQ實(shí)現(xiàn)轉(zhuǎn)發(fā)。
簡單來說kube-proxy主要在所有的Node節(jié)點(diǎn)做如下三件事:
使用ipvs作為kube-proxy后端,不僅提高了轉(zhuǎn)發(fā)性能,結(jié)合ipset還使iptables規(guī)則變得更“干凈”清楚,從此再也不怕iptables。
更多關(guān)于kube-proxy ipvs參考 IPVS-Based In-Cluster Load Balancing Deep Dive .
本文首先介紹了kube-proxy的功能以及kube-proxy基于iptables的實(shí)現(xiàn)原理,然后簡單介紹了ipvs,了解了ipvs支持的三種轉(zhuǎn)發(fā)模式,最后介紹了kube-proxy基于ipvs的實(shí)現(xiàn)原理。
ipvs是專門設(shè)計(jì)用來做內(nèi)核態(tài)四層負(fù)載均衡的,由于使用了hash表的數(shù)據(jù)結(jié)構(gòu),因此相比iptables來說性能會(huì)更好?;趇pvs實(shí)現(xiàn)Service轉(zhuǎn)發(fā),Kubernetes幾乎能夠具備無限的水平擴(kuò)展能力。隨著Kubernetes的部署規(guī)模越來越大,應(yīng)用越來越廣泛,ipvs必然會(huì)取代iptables成為Kubernetes Service的默認(rèn)實(shí)現(xiàn)后端。
轉(zhuǎn)自