摘要:本文從單機真機運營的歷史講起,逐步介紹虛擬化、容器化、Docker、Kubernetes、ServiceMesh的發展歷程。並重點介紹了容器化階段之后,各項重點技術的安裝、使用、運維知識。可以說一文講清楚服務器端運維的熱點技術。
序
文章的名字起的有點糾結,實際上這是一篇真正從基礎開始講解,並試圖串聯起來現有一些流行技術的入門文章。
目前的企業級運營市場,很有點早幾年前端工程師所面臨的那樣的窘境。一方面大量令人興奮的新技術新方案層出不窮;另外一方面運維人員也往往陷入了選擇困局,艱於決策也疲憊於跟蹤技術的發展。
目前的網絡上已經有很多新技術的介紹文章和培訓資料——絕大多數講的比我要好得多。
因為工作原因,我有比較多的用戶服務經驗。所以我要說的是,寫這篇文章的原因,不是因為現有資料不夠好。而是這些資料大多都是從技術本身出發,不斷的說“我可以提供A、我可以提供B、還有我的特征C也不錯”。而忘記了問,用戶想要的是什么,用戶想解決的問題是什么。
所以不同於通常的技術文章使用技術本身串起來所有的內容,本文試圖通過需求和技術的互動發展來串起來運維技術的發展歷程。
在整體系統中,開發和運維都是很重要的,所以現在DevOps的理念早已深入人心。但本文並不講解開發部分的內容,這里只集注在運維架構的演進方面。
即便如此,運維也是非常大的一個話題,所以我的目標再縮小一些,只限定在基礎系統軟件的領域。
一切都始於單機
最早的時候,一切程序還都很簡單,一台電腦已經足夠運行。對比起來看,就好像現在單機版的游戲。程序的執行不需要有網絡,不需要有服務器,只要保證電腦本身正常,程序就能跑的很好。企業中的運維人員,大多也是會裝電腦系統就夠。因此往往是由開發人員兼任,完全不用像現在的網管一樣睡一覺起來似乎就已經落伍了。
因為信息共享的需求,在一般企業中,最先獨立出去的是兩種應用:數據庫服務器和文件服務器。特點是,工作人員的電腦上,安裝軟件的客戶端。而數據和資源、信息文件,保存在文件服務器上。你肯定看出來了,這既是共享的需求,也是負載均衡的需求。
即便在今天,仍然有很多應用采用這種模式,比如很多的銀行、火車站售票窗口、還有常見的一些大規模網游。當然這些系統也沿着不同的道路演進着,比如大多銀行仍然喜歡使用大型機系統。這種模式通常稱為“客戶端-服務器”模式,簡稱C/S模式。
在這個階段中,對於一些大型企業和學校,無盤工作站是很流行的一種部署架構。在工作站上有顯示器、鍵盤、CPU、內存,沒有硬盤。系統的啟動由服務器提供,由客戶端網卡完成虛擬硬盤和系統引導的過程。 這樣的系統並不完全是為了節省硬盤的費用,在系統的維護上也方便很多,比如殺毒,也只要在服務器的文件區完成殺毒即可。安裝軟件,在一台工作站安裝完,在其它工作站上就可以直接共享使用。
在那個時代,Novell是最風靡的網絡系統。當前風靡的Windows Server和Linux在服務器端尚且式微。
由C/S至B/S
在客戶端安裝一個程序並且保證程序的長期正常運行是挺費勁一個事情。特別是隨着Windows系統快速的升級,和客戶端可能安裝的軟件數量增加帶來的運行環境復雜性增加,兼容性問題成為另外一個麻煩。
所以從用戶、開發人員到運維人員,都開始將程序從客戶端(Client)的方式,轉移到了瀏覽器(Browser)的方式。
這其實就是我們使用電腦瀏覽互聯網的方式,習慣了手機上網的我們,可能已經忘記了一個域名就賣出天價的時代。
瀏覽器“開箱即用”的特性很快就風靡業界。Google公司甚至專門為此研發了只支持瀏覽器環境的Chromebook上網筆記本。在深圳的華強北,大量的輕薄短小的迷你上網本也快速出貨。
因為B/S模式的開發需求快速增加,服務器有了內置WEB服務的要求。所以大量的Linux服務器和WindowsNT/200x服務器開始占領市場。
在開發語言上,也在短期內出現了一批適應互聯網編程的開發語言或技術,比如LAMP(XAMP)/C#.net+IIS/Java+Tomcat。
虛擬化
很多人認為現代的虛擬化技術起源於早期大型機時代的分時系統或者硬件虛擬化技術。其實嚴格講並非如此,上面兩項技術只是提供了一個重要的思路,比如資源共享、比如API向上層穩定兼容。從技術的演進上而言,現代所流行的虛擬化技術跟這些技術都沒有關系。
現在我們使用的虛擬化技術,主要功能是在一台電腦中模擬出來完整的另外一台或者幾台電腦。從而支持另外的、完整的操作系統在其中運行。這另外執行的操作系統,看起來就像是電腦上運行的客戶端程序一樣的效果。但在其中運行的程序,表現跟運行在獨立主機上的程序並無不同。
在CPU廠商尚未給CPU內置虛擬化(比如VT)技術之前,這種模擬器就已經出現了。我所考證最早使用這種方式工作的軟件是一款分析、破解應用軟件的軟件。是一個重要的黑客工具,因此軟件的名字在這里不提。這款軟件完整的模擬操作系統的執行,在其中運行的應用軟件完全意識不到自己是在一個虛擬的環境中執行。在這種狀態下,軟件的一切動作都可以被跟蹤、記錄而無所遁形,甚至隨時被中斷運行切換到代碼分析模式,從而快速的被破解。
這種工作方式完整的模擬一切,可以達成工作,但是因為使用了大量的CPU調試中斷,軟件模擬和監控,導致運行效率非常低。所以在生產環境中並不流行,通常主要用戶是開發部門。時至今日,仍然有一些應用采用這種方式工作,比如Android的模擬器,就是基於QEMU的虛擬化技術。不僅可以模擬x86系列CPU,還可以模擬ARM/MIPS等。
直到從CPU硬件層級實現了對虛擬化的支持之后,配合上軟件的進步,在虛擬計算機中的程序的運行效率才得到了大幅度提高。在不需要大量顯示資源的后端應用中,運行速度已經無限接近實體真機運行同樣應用的速度。而這恰恰是服務器端軟件所需要的,至此,虛擬化的推廣掃清了障礙。
虛擬化的出現大幅提高了運維效率,也大幅的提高了硬件服務器的利用率,帶來了運維的革命性變化。其實就是從這個時期開始,運維部門才逐漸同研發部門互相比肩,脫離了長期從屬的尷尬地位。
在這一時代,VMWare幾乎一家獨大。服務器端采用 VMware ESXi, 客戶端使用VMware Workstation。盜版的數量恐怕更是正版采購量的十倍以上。
即便在微服務如此普及的今天,很多企業的基礎硬件環境也會首先部署VMware ESXi,再由其中划分所需資源供微服務架構來使用。
盡管如此,在開源軟件領域,KVM、OpenStack以及其它類似衍生類應用仍然占領着不大不小但是穩定發展的一塊陣地。
原因很簡單,虛擬化實際是前幾年火熱的概念“雲服務”的基本基石。為了提供計算資源給客戶,雲服務廠商基本都需要定制功能、界面給用戶完成大量的自主操作。
這些需求,顯然是VMWare這樣的商業軟件難以實現的。因此在開源系統的基礎上進行延伸開發就成為了雲服務廠商的必由之路。
因此盡管感覺上身邊的同事、朋友都在使用VMWare系列產品,但畢竟很多企業已經選擇租用雲服務器而不建設自己的機房。可想而知,這樣的用戶數量還是很驚人的。
值得一提的是,原來C/S時代的無盤工作站也與時俱進,以“瘦客戶機”的身份在很多企業廣泛應用。這時候的瘦客戶機,並不一定沒有硬盤,重點瘦客戶端啟動之后就會執行“遠程桌面客戶端”程序,連接到服務器上虛擬化的桌面系統。從而在表現上如同一個真正的桌面電腦一樣完成正式的工作。
運維人員在這種方式下可以節省大量客戶端維護的精力。比如某個終端操作系統崩潰,可以簡單備份一下數據(數據在服務器上的,甚至無需備份),刪除這個客戶端,另外從標准模板執行一個實例出來就完成了新系統的部署。這個過程完全不需要離開座位,點擊幾下鼠標,操作就完成了。
資源復用
原本需要對大量實體服務器進行管理,網管跑機房跑斷腿。有了虛擬化之后,運維人員只要坐在電腦前點點鼠標就可以完成工作。配合上遠程管理卡(當前的品牌服務器基本已經內置),運維人員已經很少需要進機房了。而且響應速度,比跑機房直接操作實體機快了不知多少。這是上面所說,虛擬化對於運維效率的提升。
更主要的作用,是虛擬化對於服務器資源復用的幫助,這才是用“革命性”一詞來形容虛擬化的主要原因。
我們經常見到,很多應用,實際上對CPU/內存等資源的消耗並不大。比如執行WORD編輯文本的時候,CPU占用率往往不超過20%,內存用的更是很少。
對於某些最終客戶相關的應用,高峰期和低谷期的設備利用率差別巨大。
因此利用虛擬化技術,在實體機上虛擬多台服務器,分別執行應用,合理調配各應用的高峰和低谷,對於設備利用率的提高作用非常顯著。
這一簡單的理念貫穿着基礎系統軟件運維技術演進的全部歷史,今天流行的微服務,究其根本,也是在顆粒度上對應用進行了更細致的划分,從而更精確、高效的利用計算資源。
容器(Container)、LXC、Docker
虛擬化的市場巨大,雖然VMWare吃到了最大的一口。但業界的競爭從來沒有停止。
很快容器技術就脫穎而出,成為業界新寵。成功的原因是多方面的,但最硬核的一條,是對系統運行效率的提升。
在上一章我們說過,虛擬化會在一台電腦上,虛擬出1台或多台電腦,運行另外的操作系統。比如執行了2台Windows,1台Ubuntu,1台Centos。隨后在這些操作系統上,再執行具體的應用。
應用在執行的過程中,大量的硬件調用通過虛擬主機的操作系統,被虛擬化系統比如VMWare所截獲,再轉換到實體主機的硬件層執行。這個過程已經成為執行效率進一步提升的瓶頸。
而對於大多數應用來說,其規模遠遠小於操作系統本身,往往是為了執行一個只有幾十M容量的應用,首先要部署一個上G容量的操作系統。系統啟動過程所耗費的時間也是遠超應用本身。這種情況下,虛擬化對於計算資源的消耗更是變得尤為突出。
容器則是使用了完全不同的思路,在容器中,每個應用都共享了實體主機的操作系統本身。只是利用內核提供的隔離技術,完全無法發現其它應用的存在,同樣實現了“獨占”的效果。
在容器中執行的應用,所有操作實際上並不是被虛擬化的,跟直接在實體機上的執行從效率上說沒有區別。
容器技術的缺點也是明顯的,就是容器無法支持實體主機上的多種操作系統,容器中的操作系統,跟宿主機上的操作系統必須是一致的。
比如在Ubuntu主機上的容器中,可能出現Centos/Ubuntu,但不可能出現Windows容器。
容器技術發展迅猛,即便系統軟件巨頭微軟也無法忽略,傳聞微軟在Windows的容器化上投入了巨大的資源,但截至今日,尚未有說服力的產品出現。
因此實際上我們提起來容器,在今天說就是Linux容器(LXC)。
Linux容器的雛形是於1979就出現了的chroot,相信很多Unix類用戶都用過。執行chroot之后,用戶的環境會切換到指定目錄的架構環境之中,只有內核跟宿主機共用,通常是用於解決大量的版本庫兼容性上的問題。
chroot現在也有一個很流行的典型應用可能你知道,就是Android手機上的Linux For Android。眾所周知的,Android系統使用的內核就是Linux,而Linux For Andoird實際上就是首先構建一個完整的Linux文件系統,mount到指定的目錄,然后用chroot將系統切換到這個目錄,從而在手機上得到了完整的Linux功能。
2006年,Google工程師Paul Menage和Rohit Seth提出了用於進程間隔離的Process Container技術,並在Linux內核中實現,就此奠定了當前Linux容器技術的基礎。次年,為了避免用詞上的誤解,更名為Control Groups,簡稱cgroups。
當今Linux容器系統層出不窮、百團大戰的盛況,基本都是基於這些技術。因為主要技術難題在內核端已經解決,大大的降低了實現門檻,所以當前多種容器技術的比拼,都是在易用性、自動化和持續集成、以及已經容器化的資源數量上做文章。
在這些競品中,Docker是應用最為廣泛的一個,應當算是事實上的工業標准。后續很多流行的容器技術,往往是基於Docker的進一步創新。
Docker的安裝
從本小節開始,會介紹一些安裝、使用的具體知識,但本文主要目的仍然是串起來運維的知識體系。所以詳細的內容,建議到本文提供的鏈接或者參考資料中繼續學習。
Docker官網地址:https://www.docker.com
Docker最初只有一個開源免費的版本,現在Docker已經分為社區版(CE)和企業版(EE),后者功能更強,但是收費的版本。通常沒有特殊需求的情況下,使用社區版本已經足夠了。
如果只是想簡單學習、實驗,或者基於Docker的開發、測試,Docker還提供有一個桌面版。可以根據自己操作系統不同,在這里選擇下載:https://www.docker.com/products/docker-desktop,下載需要提前在網站注冊用戶。
桌面版在Mac或者Windows的執行,實際上也是使用虛擬化的方法,首先運行一個Linux的虛擬機,然后在虛擬機中使用Docker的容器功能。並且有Mac/Windows的命令行程序配合Docker的操作。原因前面已經說了,目前來講,容器技術還只能在Linux中使用。
Docker的生產環境,當然就只能選擇Linux系統。通常如果是研發人員主導的項目,較多會選用Ubuntu系統。因為Ubuntu系統默認配置客戶端工具豐富,桌面絢麗多彩,好看又好用。各組件的升級包發布非常快,能夠快速接觸新的技術和解決存在的Bug。不過對於運維來講,快速發布的補丁包實際上代理了額外的工作量,所以在此建議使用Ubuntu系統作為生產環境的話,一定要使用長期支持的LTS版本。
如果是運維人員主導的項目,大多還是會選擇Centos。Centos相當於RedHat的社區免費版本。系統穩定可靠,較少的默認工具也讓系統占用較少的資源。對於系統各組件來講,Centos更新會比較慢,每次的更新也會經歷認真、全面的測試,適合於生產環境的穩定、安全需求。
除此之外,因為容器主機對主機本身並沒有多少操作的需求和工具的要求,所以現在還有很多極簡定制版本的Linux用於Docker的容器主機,比如CoreOS,以及各大IT廠商自己定制的版本。在這些系統中,大多都已經預置了Docker系統,基礎Linux環境往往只有幾十M的容量;使用BusyBox替代大量的Linux基礎命令庫;只保留必要的管理工具和Docker啟動所使用的基本依賴庫。從而把寶貴的內存和硬盤留給容器使用。這種方式很類似於VMware ESXi所使用的方式,可見,各技術之間也在互相的學習和互相的啟發。
Ubuntu和Centos雖然沒有默認安裝Docker,但新本版的系統,比如Ubuntu14.04、Centos7之后,在軟件倉庫中都已經有了Docker的安裝包。簡單幾行命令就能完成Docker的安裝配置。
Centos之下安裝Docker:
//更新軟件源的索引
sudo yum update
//安裝Docker
sudo yum install docker
//啟動Docker服務
sudo systemctl start docker
Ubuntu下面安裝Docker:
//更新軟件源
sudo apt update
//安裝Docker
sudo apt install docker.io
這種情況下安裝的Docker一般都不是最新的版本,但通常都是夠用的。所以如果沒有特殊的需求,我建議直接這樣安裝、使用就好。
如果希望安裝最新的版本,可以添加Docker官方源,然后安裝最新的CE版本。這方面請參考官方文檔:https://docs.docker.com/install/overview/。官方文檔中,左側的目錄里,有針對不同操作系統的安裝方法,請自行學習。
對於大多數應用,我非常建議的學習方式是以閱讀官方的文檔為主。如果因為英語水平問題希望閱讀中文資料,也盡量同官方文檔對照着看。因為軟件版本更新快速和翻譯中的錯誤問題,僅僅閱讀中文翻譯文檔往往會有一些讓你費解的問題存在。
Docker典型使用
完整的學習Docker建議參考官方文檔:https://docs.docker.com/get-started/
文檔寫的非常棒,圖文並茂,清晰易懂。下面只對典型的應用場景做一個串講。
首先是基本概念:
Container: 容器,相當於一台虛機,可能正在執行,也可能執行結束已經退出(關機)。
Image: 映像。這個是新手容易困惑的。因為在VMWare中沒有這個概念,比較接近的,可以理解為已經安裝好的操作系統模板,每次啟動一個容器(虛機),實際是復制一份映像,然后在映像上執行。前面已經說過,容器本質上是跟宿主機共享內核,映像則相當於硬盤的文件系統,當然是建立在某目錄下的根文件系統,只是文件本身,並不是磁盤的克隆。
Docker: Docker是這套容器系統的產品名稱,也是命令行管理工具的名稱(命令行工具要使用全小寫字符)。所有對Docker系統的操作,都可以通過命令行完成。直接執行docker
,可以得到概要幫助文檔。
(本文的代碼塊中,命令前面的#符號是系統root狀態的提示符,不需要用戶輸入)
# docker
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Options:
--config string Location of client config files (default "/Users/andrew/.docker")
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/Users/andrew/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/Users/andrew/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/Users/andrew/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quit
Management Commands:
builder Manage builds
checkpoint Manage checkpoints
config Manage Docker configs
container Manage containers
image Manage images
manifest Manage Docker image manifests and manifest lists
network Manage networks
node Manage Swarm nodes
plugin Manage plugins
secret Manage Docker secrets
service Manage services
stack Manage Docker stacks
swarm Manage Swarm
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes
Commands:
attach Attach local standard input, output, and error streams to a running container
build Build an image from a Dockerfile
commit Create a new image from a container's changes
cp Copy files/folders between a container and the local filesystem
create Create a new container
deploy Deploy a new stack or update an existing stack
diff Inspect changes to files or directories on a container's filesystem
events Get real time events from the server
exec Run a command in a running container
export Export a container's filesystem as a tar archive
history Show the history of an image
images List images
import Import the contents from a tarball to create a filesystem image
info Display system-wide information
inspect Return low-level information on Docker objects
kill Kill one or more running containers
load Load an image from a tar archive or STDIN
login Log in to a Docker registry
logout Log out from a Docker registry
logs Fetch the logs of a container
pause Pause all processes within one or more containers
port List port mappings or a specific mapping for the container
ps List containers
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rename Rename a container
restart Restart one or more containers
rm Remove one or more containers
rmi Remove one or more images
run Run a command in a new container
save Save one or more images to a tar archive (streamed to STDOUT by default)
search Search the Docker Hub for images
start Start one or more stopped containers
stats Display a live stream of container(s) resource usage statistics
stop Stop one or more running containers
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
top Display the running processes of a container
unpause Unpause all processes within one or more containers
update Update configuration of one or more containers
version Show the Docker version information
wait Block until one or more containers stop, then print their exit codes
Run 'docker COMMAND --help' for more information on a command.
需要注意的是,因為容器的特征,Docker大多數用法都需要在Linux的root權限下執行,所以請提前使用sudo su
進入到root狀態。Windows/Mac的桌面版因為實際使用的是虛機執行,當前本機的Windows/Mac只是一個顯示終端,所以不需要。像上面一樣只顯示幫助文檔,是少數幾個不需要root權限執行的用法之一。
命令行管理工具雖然是所有docker功能的執行起點,但docker本身實際是一個后台的服務。這個服務可以運行在任意電腦上。只要docker命令行管理工具配置后能夠同后端服務連接、通訊就可以完成管理工作。這也是Mac/Windows版本的Docker桌面版本管理工具的運行模式,后端服務運行於虛機中的Linux中,真正在Mac/Windows操作系統執行的是這個命令行管理工具。
執行一個容器,當然是從准備容器的映像文件開始。映像文件或者自己制作(相當於在VMWare在虛機中安裝操作系統),或者使用別人制作完成的。Docker提供了制作工具,我們后面再講。
獲取別人制作的映像文件通常兩個辦法:一是從映像倉庫直接下載;二是導入他人導出的映像文件包。
比如我們想運行一個Ubuntu容器,首先在官方的容器倉庫搜索:
# docker search ubuntu
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
ubuntu Ubuntu is a Debian-based Linux operating sys… 9298 [OK]
dorowu/ubuntu-desktop-lxde-vnc Docker image to provide HTML5 VNC interface … 279 [OK]
rastasheep/ubuntu-sshd Dockerized SSH service, built on top of offi… 205 [OK]
consol/ubuntu-xfce-vnc Ubuntu container with "headless" VNC session… 158 [OK]
ansible/ubuntu14.04-ansible Ubuntu 14.04 LTS with ansible 96 [OK]
ubuntu-upstart Upstart is an event-based replacement for th… 96 [OK]
neurodebian NeuroDebian provides neuroscience research s… 56 [OK]
1and1internet/ubuntu-16-nginx-php-phpmyadmin-mysql-5 ubuntu-16-nginx-php-phpmyadmin-mysql-5 49 [OK]
ubuntu-debootstrap debootstrap --variant=minbase --components=m… 40 [OK]
nuagebec/ubuntu Simple always updated Ubuntu docker images w… 23 [OK]
tutum/ubuntu Simple Ubuntu docker images with SSH access 19
i386/ubuntu Ubuntu is a Debian-based Linux operating sys… 16
1and1internet/ubuntu-16-apache-php-7.0 ubuntu-16-apache-php-7.0 13 [OK]
ppc64le/ubuntu Ubuntu is a Debian-based Linux operating sys… 12
eclipse/ubuntu_jdk8 Ubuntu, JDK8, Maven 3, git, curl, nmap, mc, … 8 [OK]
codenvy/ubuntu_jdk8 Ubuntu, JDK8, Maven 3, git, curl, nmap, mc, … 5 [OK]
darksheer/ubuntu Base Ubuntu Image -- Updated hourly 5 [OK]
pivotaldata/ubuntu A quick freshening-up of the base Ubuntu doc… 2
1and1internet/ubuntu-16-sshd ubuntu-16-sshd 1 [OK]
paasmule/bosh-tools-ubuntu Ubuntu based bosh-cli 1 [OK]
smartentry/ubuntu ubuntu with smartentry 1 [OK]
ossobv/ubuntu Custom ubuntu image from scratch (based on o… 0
1and1internet/ubuntu-16-healthcheck ubuntu-16-healthcheck 0 [OK]
pivotaldata/ubuntu-gpdb-dev Ubuntu images for GPDB development 0
1and1internet/ubuntu-16-rspec ubuntu-16-rspec 0 [OK]
Docker默認的倉庫是官方的Docker Hub,包含了大量的官方映像和社區貢獻的映像。出於安全和穩定性方面的原因,再加上訪問國外網站的速度問題,通常稍有規模的公司都會建立自己的映像倉庫,這方面的內容請參考官方文檔。
上面的命令執行后,可以搜索到大量的Ubuntu相關映像。如果你已經知道你需要使用哪一個,比如相關文檔中已經說明了,可以直接使用。如果不了解,可以按照這樣的原則來選擇:
- 盡可能選擇官方出品的,也就是OFFICIAL一欄標注了OK的。
- 選擇推薦度比較高的,也就是STARS的值比較高的。
- 在名稱和描述中查看符合自己需要的特征。比如我經常給開發人員推薦dorowu/ubuntu-desktop-lxde-vnc,這個是內置了桌面圖形界面環境的一個Ubuntu影響,不僅可以使用VNC客戶端直接訪問,還內置了一套web版本的vnc客戶端。直接使用瀏覽器訪問就能得到一個全功能的桌面Ubuntu環境。
假設我們希望使用第一個官方出牌,推薦度最高的映像,可以使用下面命令將映像從官方倉庫下載到本地電腦:
# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
6cf436f81810: Pull complete
987088a85b96: Pull complete
b4624b3efe06: Pull complete
d42beb8ded59: Pull complete
Digest: sha256:7a47ccc3bbe8a451b500d2b53104868b46d60ee8f5b35a24b41a86077c650210
Status: Downloaded newer image for ubuntu:latest
pull是docker命令的子命令,表示從倉庫中下拉映像,后面的ubuntu是搜索到的映像名稱。
映像的下載時間依賴網絡速度,通常都非常快,主要原因是容器的映像,遠遠小於VMWare的虛機容量。比如這個Ubuntu的映像,通常是88M左右(因版本變化會有不同)。Docker的映像文件是分層增量存儲的,所以在上面的下載過程中,能看到多個獨立的下載進度。
現在流行的微服務概念,是從研發端架構設計開始就遵循的一系列方法的統稱。“微”這個字在其中有很多含義。但大多資料做概念解釋的時候,都忽略了容器虛擬化本身的“微”。其實稍微多了解一點就會知道,如果沒有容器本身的輕量、高效,很多后續的手段根本就用不上。
映像包下載完成后,可以使用下面命令列表顯示:
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/ubuntu latest 47b19964fb50 4 weeks ago 88.1MB
這表示我們本地有了一個映像,名稱為“ubuntu”。
本地的映像文件,可以使用save子命令保存為一個tar文件,用於備份或者提供給其他網絡不方便的用戶使用:
# docker save ubuntu -o ~/Downloads/docker.ubuntu.tar
load子命令可以將一個docker存儲的tar文件載入到本地的映像倉庫,如果本地倉庫中已經有了同名的映像文件,則倉庫中的映像文件會被覆蓋:
# docker load -i ~/Downloads/docker.ubuntu.tar
這也是剛才提到的,第二種獲取映像文件的方法。因為國內的網絡情況,很多國外的映像資源,無法直接使用pull子命令獲取。往往需要別人幫助,然后load到本地映像倉庫。
有了映像文件,可以使用下面的命令啟動一個容器,也就是相當於VMWare中的啟動一個虛機:
# docker run -it ubuntu /bin/bash
root@519279bc7105:/#
run是docker的另外一個子命令,表示執行一個容器映像。-it是命令行參數,表示分配一個終端用互動的方式執行。docker run --help
可以更詳細的顯示有關run子命令的文檔。接下來的ubuntu就是剛才下載的映像文件名稱。
最后的“/bin/bash”是容器啟動后,在給定的根文件系統中,執行的程序入口。這個可能會讓VMWare用戶費解,原因依舊是VMWare中沒有這個概念,VMWare虛機啟動時候當然是如同一台真正的電腦一樣從頭開始啟動一個操作系統。
而Docker容器我們剛才說了,實際上是跟宿主機共享內核,然后部署一個完整的根文件系統,這個環境實際上是靜態的。只有在這個環境中真正運行起來一個程序,才代表了容器的運行。上例中,/bin/bash這個外殼交互程序,就是作為了我們這個容器的執行入口。
這時候如果另外開一個命令行窗口,執行容器列表子命令ps,可以觀察到ubuntu映像的執行:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abae98b3ba33 ubuntu "/bin/bash" 11 seconds ago Up 10 seconds inspiring_lalande
在ubuntu的執行窗口,你可以試着做一些操作。如果對Ubuntu足夠熟悉的話,你會發現,這是一個高度精簡的Ubuntu系統,通常稱為Ubuntu core,提供用戶在其上部署自己的應用。這是微服務系統的一個重要理念--每個微服務的設計中,盡力只包含最必須的那些文件系統和庫。從而節省空間、提高效率、更提高了安全性和穩定性。我見過幾個印象深刻的系統,其中連bash外殼程序都沒有,即便程序中有未測出的漏洞被黑客利用,因為缺乏大量基本必須的系統操作工具,配合上只讀的文件系統,黑客什么也做不了。
在容器內的操作完成后,ctrl+d或者exit可以退出容器的bash環境,回到宿主機的命令行。
這時候再次查看容器列表,你會發現剛才的容器已經消失了:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
這是因為,默認情況下,ps子命令只列出正在運行中的容器。增加-a參數可以列出所有的容器,包括已經執行結束退出的:
#docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abae98b3ba33 ubuntu "/bin/bash" 8 minutes ago Exited (0) 14 seconds ago inspiring_lalande
這是容器的另外一個重要特征,當指定的程序運行結束后,退出了程序,也就代表了容器的關閉,或者說停止運行。這在VMWare虛機中同樣沒有相同概念,傳統的虛機即便退出了應用程序,除非顯式的關機了,否則操作系統還維持着虛機的繼續運行。
至此我們完成了一個容器的完整執行流程,對比傳統的虛機系統,你可能首先的感覺就是“快”。容器的啟動速度快、退出速度更快。現在微服務系統的大規模集群、接近實時的動態部署調整規模等特性都得益於於容器的這種特征。在傳統的虛機環境中,這是不可想象的。
重新啟動一個已經退出的容器使用start子命令:
# docker start abae98b3ba33
abae98b3ba33
#
參數abae98b3ba33是剛才ps子命令列表中所得到的容器的ID號,這個ID號伴隨一個容器的生命期都不會改變。在docker命令行中使用ID號的時候,實際上只需要輸入頭幾位、跟其它ID不會混淆就可以執行。
start子命令執行后,首先返回一個ID號,然后又回到了命令行提示符。這是因為這個鏡像是在系統后台執行的。
使用ps子命令查看:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abae98b3ba33 ubuntu "/bin/bash" About an hour ago Up About a minute inspiring_lalande
可以看到容器已經在后台執行了。實際上這才是容器通常的狀態,我們剛才使用-it運行bash外殼來執行一個容器,只是為了讓你看起來更直觀。正常的應用,比如一個web服務器,執行一個命令行外殼出來是沒有意義的。
如果想回到容器的命令行,來觀察應用的運行狀態,這通常是用於調試。可以使用attach子命令:
# docker attach abae
root@abae98b3ba33:/#
注意這次參數使用了容器ID的簡寫形式:abae。
你肯定注意到了,從容器的bash使用ctrl-d或者exit退出后,容器再次退出了。原因是attach子命令實際上是附着到容器的執行進程上去了,跟我們開始使用-it執行bash是完全相同的。
這顯然不適合出於調試目的進入容器的操作。況且,一個容器啟動的時候,執行的往往也不是bash外殼程序,而是應用的啟動程序。attach到應用的啟動程序,往往也達不到調試的目的。
在這種情況下,使用exec子命令,另外執行一個shell才是合理的選擇:
(代碼塊中的//符號是注釋的意思,該行信息並不需要輸入,也不是系統給出的提示信息)
//當然首先還是要把停止的容器啟動起來,不然exec子命令是無法執行的
# docker start abae
//因為通常/bin目錄都在PATH環境變量中,所以實際上只執行bash就等於執行了/bin/bash
# docker exec -it abae bash
root@abae98b3ba33:/#
這一次,如果你退出了容器的bash,你會發現,容器還正常的在后台運行着:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
abae98b3ba33 ubuntu "/bin/bash" About an hour ago Up 29 seconds
其它幾個經常會用到的容器執行方式:
//運行一個ngnix容器,並命名為mynginx
//下次對容器的操作,可以直接使用命名,而不是容器ID,這非常適合寫持續集成的批處理
// -d是生產中經常會用到的在后台執行容器,不做互動也不分配終端
# docker run --name mynginx -d nginx
//將容器的80端口映射到宿主機8080端口
//docker有完整的網絡組件,可以為容器分配網絡地址,請查看相關資料
//但根本上,直接使用宿主機IP,在其中開服務端口才是最高效的方式
# docker run -p 8080:80 -d nginx
//如果宿主機有多個網址,還可以指定端口綁定的IP地址
# docker run -p 211.100.132.14:8080:80 -d nginx
//除了映射端口,另外映射宿主機的/webdata文件夾到容器的/var/www/html文件夾
//作為可負載均衡的集群,文件的持久化及共享,通常都使用宿主機的文件夾映射到容器來完成
# docker run -p 8080:80 -v /webdata:/var/www/html -d nginx
//在前面的基礎上,指定容器的重啟策略為永遠
//這個可以保證當容器意外退出甚至宿主機重啟后,容器也會被重啟起來
# docker run -p 8080:80 -v /webdata:/var/www/html -d --restart=always nginx
當一個容器確定不再需要后,可以使用rm子命令刪除,注意如果容器中還有運行所產生的數據,這些數據也會刪除,這跟傳統虛機是一致的:
//首先要使用stop停止容器的運行,在執行中的容器是不能被刪除的
# docker stop abae
abae //這是docker命令執行后的回顯
//刪除容器及其數據
# docker rm abae
abae
#
映像文件不再使用后,同樣也可以刪除。注意如果有容器正在使用的映像,是不可以被刪除的,這種情況需要停止所有使用到該映像的容器,並刪除容器之后,才可以刪除映像文件:
docker rmi ubuntu
自己制作容器映像
不同於傳統虛機從光盤啟動系統開始一個操作系統的安裝,當然我們也已經知道了那是很浪費資源而並無意義的一件事情。
容器沒有操作系統的啟動過程,因此傳統的系統部署手段都是不能使用的。所以通常上,容器映像的建立,都是基於某個已有的系統。同樣的,我們建立的映像,也可能成為別人,或者我們自己,將來建立映像時候的基礎。所以前面所講Docker映像文件,采用分層增量的組織方式,的確是非常合理的。
如果對於裁剪Linux經驗還不足,我很建議你從最常用的centos或者ubuntu映像為基礎開始安裝自己的應用。
建立映像文件規范的方法是使用docker的build子命令,將編寫的Dockerfile建立成映像文件。Dockerfile的內容相當於一組腳本命令。為了描述原理,我們先從更底層的笨辦法開始。
因為我們已經下載了ubuntu的映像,所以我們以ubuntu為例來看一個映像的建立過程。
首先是執行一個ubuntu的容器,這個上一小節就見過了:
# docker run -it ubuntu bash
root@bded292dfa72:/#
隨后出現了ubuntu容器的提示符,我們接下來的操作都在容器的bash外殼中執行。
//更新軟件源
apt update
apt dist-upgrade -y
//安裝nodejs和包管理器,nodejs是npm的依賴項,會自動被安裝
//另外安裝一個vim編輯器,方便編輯測試程序
apt install npm vim
//轉換到工作目錄
cd /opt
//編輯nodejs的測試程序
vi server.js
測試程序輸入以下內容,輸入完成后使用:x
保存退出:
#!/usr/bin/env node
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World<br>Here is NodeJs in Docker<br>\n');
}).listen(8080, '0.0.0.0');
console.log('Server running at http://127.0.0.1:8080/');
繼續后續操作:
//將nodejs程序設置為可執行
chmod +x server.js
至此測試映像所需的安裝工作已經完成,可以exit退出容器的bash,同時容器也會停止執行。
接着可以使用commit子命令將一個容器的當前狀態,在本地映像倉庫保存為一個映像:
# docker commit -m "a nodejs helloworld" --author="andrew" bded292dfa72 nodejshello
sha256:5b486e5ec498739c8c35c36a1c47bab19ca2622846de12ae626cf8a4fc4376bc
#
-m參數之后為映像文件的提交信息,可以理解為映像文件的描述信息;--author是映像的作者信息;接着是容器ID;最后是保存的新映像的名字,注意必須使用小寫字母。
commit子命令的執行需要一點時間,這取決於映像的大小,執行完成后會返回映像的sha256哈希值。
此時查看映像列表,可以看到多了一個映像,也就是我們新建的nodejshello:
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nodejshello latest 5b486e5ec498 57 seconds ago 473 MB
docker.io/ubuntu latest 94e814e2efa8 7 hours ago 88.9 MB
我們來執行、測試一下新建立的映像:
# docker run -p 8080:8080 -d --restart=always nodejshello /opt/server.js
e64622cde5124e3fcd70e74a3d687e32f347dfc12f13a55b1f593ee2e7fe4553
容器的啟動速度非常快,馬上你就可以看到返回的一個hash值,也就是容器的ID。
此時查看容器列表,能看到已經執行的容器,顯示的信息中還有端口的映像信息:
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e64622cde512 nodejshello "/opt/server.js" 7 seconds ago Up 6 seconds 0.0.0.0:8080->8080/tcp wizardly_aryabhata
使用curl命令來查看一下執行結果:
# curl 127.0.0.1:8080
Hello World<br>Here is NodeJs in Docker<br>
#
可見,server.js已經正常執行了。
以上是手工建立Docker映像文件的方法。使用build子命令自動化建立映像文件,建議參考一下官方的文檔:
https://docs.docker.com/engine/reference/builder/
我們仿照手工建立映像的例子,只展示一個簡單的Dockerfile構建映像的過程。
首先需要做一個准備工作。Dockerfile是一個自動化的執行文件,肯定無法像上面一樣手工編輯程序。所以這個程序我們需要提前編輯好,方便起見,還要提前放到我們的內部文件服務器上。比如我們最終放置的地址是:http://192.168.1.100/share/nodejs/server.js
。
在生產環境中,較多應用是放置在git倉庫中,開發團隊將代碼提交到git倉庫;運維人員在Docker映像構建時從倉庫中拉出來編譯、部署。中間當然還有測試人員的測試過程,這個是由企業的開發流程決定的,此處不談。
請看我們編寫的Dockerfile:
FROM ubuntu
RUN apt update
RUN apt dist-upgrade -y
RUN apt install -y npm wget
WORKDIR /opt
RUN wget http://192.168.1.100/share/nodejs/server.js
RUN chmod +x server.js
EXPOSE 8080
ENTRYPOINT ["/opt/server.js"]
Dockerfile的內容,按照英文字面意思解釋應當都可以理解。其中最后一行ENTRYPOINT,我們指定了映像文件的執行入口,這樣我們將來執行這個映像的時候,就可以不需要顯式的使用run參數給出了。
將Dockerfile保存到一個工作目錄,然后在該目錄中執行建立映像的命令:
# docker build -t test/hello:v1 .
參數-t之后是將要生成的映像名稱,這次我們使用了一個完整的名稱,test是倉庫名稱(可以當做本地的分類名稱),斜線之后的hello是映像名稱,冒號之后則是版本號,這些必須都是小寫。其實只使用一個簡寫的名稱hello,只要沒有重名也是可以的。在一個大系統中,規范的命名習慣當然是必要的。最后的參數“.”以當前目錄的Dockerfile為模板來生成映像文件。
映像生成的過程視網速不同通常需要執行一定時間,這個過程屏幕的輸出能看到,這跟我們剛才手工建立映像文件的過程是完全相同的。
一直到最后幾行,會顯示類似這樣的內容:
...
Step 9/9 : ENTRYPOINT /opt/server.js
---> Running in c10d3d6599dd
---> 74afed533165
Removing intermediate container c10d3d6599dd
Successfully built 74afed533165
大意是,一共9個步驟都執行完成。映像提交到本地倉庫后,刪除了用於生成映像的臨時容器c10d3d6599dd。生成的容器ID是74afed533165。
查看當前的容器列表:
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test/hello v1 74afed533165 3 minutes ago 426 MB
nodejshello latest 5b486e5ec498 44 minutes ago 473 MB
docker.io/ubuntu latest 94e814e2efa8 8 hours ago 88.9 MB
新生成的映像文件已經存在了,執行容器做一個測試:
// 后台執行容器
# docker run -p 8080:8080 -d test/hello:v1
// 測試服務
# curl http://127.0.0.1:8080
Hello World<br>Here is NodeJs in Docker<br>
#
結果說明,我們制作的映像已經成功執行了。
Dockerfile的編寫簡單易用,能夠將部署和發布流程固化下來,非常方便在devOps和持續集成項目中應用。但也有一些小缺點,一是腳本本身的調試並不算非常方便,通常之前都要手工先仔細驗證才能編寫到Dockerfile;另外仍然跟我們的網絡環境有關,大量的環境安裝依賴國外的軟件倉庫,經常碰到網絡超時或者臨時性網絡故障導致映像構建失敗的情況。
通常大一些的公司都會在境外租用服務器,把映像的構建工作在國外完成,隨后再下載到本地使用;或者使用Docker Hub配合github的自動構建功能在雲端完成構建過程。這個超出本文的范圍,請在有需求的時候在網上搜索相關介紹資料。
Docker是一個功能豐富的容器系統,這里只介紹了最常用的部分。應用場景比較復雜的用戶建議盡早閱讀官方文檔,深入了解Docker的使用。
容器編排和管理
Docker的命令行方式當然很方便自動化運維,但對普通用戶講並不友好。
此外當Docker宿主機多起來的時候,管理起來顯然很不方便。
還有就是隨着微服務理念的大量應用,Docker基本系統對於批量的啟動容器、負載均衡、健康檢查、服務發現都非常無力。
這些問題在運維的歷史上其實都有對應的解決方案的。比如負載均衡可以用Nginx代理、HaProxy甚至硬件的負載均衡器。
從一台主機出發,對所有的宿主機集中管理也可以采用常用的“堡壘機”方式配合大量腳本。
但這些方案看上去都很有“補丁”的感覺,用起來並不方便,更不友好。
所以一時間,大量基於Docker容器的管理系統層出不窮。當然這也有內核解決了主要的關鍵技術問題,使得開發門檻較低的因素。
這些系統集中解決了多宿主機集群情況下的統一管理和容器動態編排方面的問題。因為Linux本身就是開源軟件,這些系統大量的采用了開源軟件資源,加上自主開發的部分代碼。所以實際上看起來很多部分都是雷同或者類似的。
從軟件工程的角度來看,VMWare模式是典型的“教堂模式”,容器當然就是“集市”模式。如果說Linux作為集市模式的代表,其領導地位還存在爭議的話。在虛擬化平台的生產應用方面,“集市”模式已經顯著領先了。
經過了2年左右的競爭、淘汰,已經有很多平台人氣不在了,國內我也知道幾家創業公司悄然倒閉。對用戶來講,最可惜的是付出的學習成本。
當前用戶比較多的系統中,比較有名的是Rancher、Kubernetes、Swarm。
Swarm是Docker官方推出,可惜推出的比較晚,很多用戶已經有了自己的選擇,不願意付出遷移的成本。否則本來Swarm是最有機會一統天下的。
Kubernetes今天看起來似乎已經成為了業界領袖,占有最多的用戶群。畢竟Google這么多年最大規模的服務器集群運營經驗不可小視。所以接下來我們就看看Kubernetes的解決方案。
Kubernetes的基本概念
Kubernetes(以下簡稱k8s)已經開始了文檔的中文化工作,截至本文成稿的時間,大多數基本的文檔都已經有了對應的中文版。並且得益於容器技術的成熟和輔助工具的完善,新版本的安裝、使用也更容易。
官方文檔位於:https://kubernetes.io/zh/docs/ 建議及早閱讀。
本文還是立足串講最常用的使用場景和相關知識。這部分的概念介紹比起Docker來要多了很多。並且沒有采用通常的技術文章使用的分類辦法,很多相鄰概念也並不是同一個層次。但原則上,是需要使用者了解和操作的會多介紹。雖然可能很重要,但通常用戶日常根本用不到的,就少介紹或者不介紹。
額外再強調一下前面說過的,k8s同其它很多容器管理平台類似,都大量的采用了社區貢獻的成熟組件。Google自主開發的部分所占比例並不大。從這個角度上說,學習一種新的容器管理平台並不會有很多瓶頸,因為很可能大多數功能、用法,你都已經接觸過了,並不陌生。
k8s運行的硬件環境:
Linux類操作系統。2G以上內存;主節點需要至少2個CPU。集群內的機器必須有完全的網絡鏈接。如果使用k8s的輔助工具協助安裝,建議是Debian/Ubuntu或者Redhat/Centos服務器,以便使用APT或者YUM工具。
k8s需要至少一台主節點(master),用於進行集群管理。k8s支持高可用模式,這種方式可以支持多台服務器共同做master節點。
經過設置,k8s可以只有一台服務器,同時做主節點和工作節點,通常是用於學習和測試。但默認設置工作節點是需要其它的服務器。
k8s的組成比較復雜,如果在虛擬化的初期,用戶維護這樣一個系統恐怕需要一個小團隊。現在官方把所有組件都合理打包成了幾個Docker映像文件。部署k8s系統實際就是下載和啟動相應的Docker容器。即便這樣,手工安裝依然有不小的工作量。很幸運官方又推出了專門的集群管理工具:kubeadm,這個命令行工具進一步簡化了k8s部署的工作。當前如果在已有的Linux服務器上部署一套k8s只要一名工程師不超過10分鍾的操作。
kubeadm幫助集群管理員簡化集群的安裝、維護工作。對於k8s容器的用戶,則是使用kubectl。這同樣是一個命令行工具,幫助容器用戶完成幾乎所有容器相關的操作。這樣分別的提供管理工具,也有利於企業在運維層面更精細的權限管控。
還有一個模塊是我們在集群搭建的時候會安裝的kubelet,這個應用通常不需要k8s用戶自己直接執行。這相當於一個后台服務,運行在所有k8s節點上,進行底層的通訊和根據通訊對所在的節點進行所需操作。
除了這三個組件,還需要手工安裝的是網絡插件。這個網絡不是Linux本身已經包含的網絡支持。這里指的是容器之間為了通訊而需要的虛擬網絡設備。我們前面一再提到過,容器技術實際上基於chroot和cgroups。這些技術其實並不包含網絡。早期的Docker本身的確也並不提供網絡支持。你可以想象原本運行在一台電腦上的兩個程序,有大把的手段進行數據共享。而現在隔離成了兩台虛擬電腦上運行的兩個獨立程序,網絡交互就是必須品了。
隨后因為需求一直都在,大量支持容器的網絡功能包出現。這樣多不同團隊開發的網絡包組件,完成的都是同樣的功能,各自有自己的粉絲群體。所以現在k8s允許用戶自行選擇喜歡的網絡插件,以提供容器間的網絡通訊。
你可以理解為,有了網絡插件,容器虛機中才能有自己獨立的ip地址,才能進行虛擬服務器之間的數據交換。k8s支持的網絡插件列表可以在這里找到:https://kubernetes.io/docs/concepts/cluster-administration/addons/
以上是k8s的基本組成。
圖形界面的粉絲們也可以歡呼了,k8s的Dashboard組件提供了WEB UI功能,容器用戶也能像VMWare用戶一樣靠鼠標完成工作了。我見過好幾個用戶對於k8s的容器自動編排、負載均衡等並不是很需要,純粹就為了使用Dashboard而配置k8s集群。Dashboard是單獨的一個Pod,沒有Dashboard並不影響k8s集群的運行,是在集群安裝完成后單獨部署執行的。
類似於使用Docker的核心就是操作容器。k8s操作的核心單元是Pod。一個Pod是k8s操作的最小單元,包括一個或者多個容器、存儲、網絡、服務。可能有多個容器的原因是,稍復雜的應用,可能都不是一個容器可以承載的。強制放在一個容器里,往往也增大了粒度,降低了資源使用效率。比如通常一個小網站,可能至少有一個WEB服務器,然后還有一個數據庫服務器。這就是兩個容器。如果是在Docker中,你可能需要分別啟動兩次,而在k8s之中,這兩個容器可以屬於一個Pod,統一進行管理。此外比較重要的一個概念,k8s可以根據要求,一次啟動Pod的多個實例,相當於對於某個應用投入了更多的計算資源,從而應對高負荷應用。
Service是k8s管理的另外一個對象,容器的運行,歸根結底是對外提供服務。網絡服務實際上就是開放某個端口,通過端口對外提供服務。不同於Docker中簡單的端口映射。k8s中可以啟動某Pod的多個實例,這些Pod中的程序,肯定對外提供了同一個接口進行服務。Service可以提供基本的負載均衡功能,把外部連接進來的訪問流量調度到不同的Pod上,讓集群的規模化發生作用。而如果在Docker上,則只能自行配置外加的負載均衡方案來達成同樣的功能,麻煩就不說了,Docker及負載均衡方案之間的銜接使得容器列表發生變動的時候很可能發生服務的阻塞。k8s的負載均衡是使用內部的DNS服務(用於動態解析Pod的IP地址,相當於服務發現)和Kube-Proxy(將流量動態分配到不同的Pod)共同來完成的。
Volume就是用於映射到容器內工作目錄的存儲目錄或者設備。k8s在Docker的基礎上根據需要更加細分了存儲設備的類型,包括普通的目錄映射、iSCSI之類的硬件存儲設備、甚至商業雲存儲服務。這讓容器技術更具有實用性。
Namespace允許為Pod添加更細致的分類管理。這在大的集群中非常必要,想想如果沒有Namespace,幾千台容器在列表中是怎樣恐怖的場景。
k8s集群的安裝
前面已經說過生產使用的集群環境最好使用redhat/centos或者專門定制的Linux環境。不過簡化操作和保持一致性,這里還是以Ubuntu18.04LTS為例來簡單描述一下k8s的安裝過程。
步驟一:基本准備工作
1.首先是設置集群中每台設備的IP地址,我們這里使用兩台服務器來做集群安裝的示范:
master 10.96.200.150
node1 10.96.200.160
IP地址並沒有特別要求。但通常上,一個實體機集群的IP地址應當綜合考慮,位於獨立的地址段。大集群的內部安全問題如果也是重點的話,還應當考慮划分VLAN做內部的IP地址隔離。
考慮到集群可能會逐步擴展,所以在master的地址之后建議空出來部分,以便將來集群增大之后增加master節點的數量。
Ubuntu18.04版本的IP地址設置做了比較大的改變。不再使用/etc/network/interfaces的配置文件。而轉為使用/etc/netplan/下的yaml文件。
netplan之下可以有多個yaml文件,用於更精細的管理多種環境、多網卡的應用場景。通常剛安裝完系統,默認只有一個文件,這個名字可能因具體版本和設置不同不一樣。我們的設置比較簡單,就直接在這個文件基礎上編輯就好,請參考下面的內容:
//大多的安裝工作需要使用root權限,所以先進入root模式
$ sudo su
//顯示配置文件內容
# cat /etc/netplan/01-netcfg.yaml
//以下是文件的內容
# This file describes the network interfaces available on your system
# For more information, see netplan(5).
network:
version: 2
renderer: networkd
ethernets:
ens33:
dhcp4: no
addresses: [10.96.200.150/24]
gateway4: 10.96.200.1
nameservers:
addresses: [10.96.200.1]
這種設置方式很簡單,相信你一看就懂。請根據你的網絡情況,正確設置這個配置文件。
設置完成后,讓配置文件生效:
# netplan apply
2.設置主機名稱。k8s集群間的內部通訊是使用主機名完成的,管理的時候也是如此。所以建議首先設置有意義、易識別的主機名稱,特別注意集群間不要重名。
在Ubuntu18.04中,主要有兩個文件需要設置,一個是/etc/hostnamew文件,其中只有一個主機名。
此外是/etc/hosts文件中,127.0.1.1之后的第二個名字。
比如我為集群主機設置的名稱為userver-master,請參考下面兩個文件的內容:
# cat /etc/hostname
userver-master
# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 userver.lan userver-master
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
3.關閉系統的swap功能。這個功能相當於Windows下的虛擬內存,當內存不足的時候,系統使用交換分區來把內存中不常用的部分交換到磁盤上去。
對於通常的系統而言,內存交換很重要,很多大型的軟件嚴重依靠虛擬內存機制。
但對於一個容器服務器系統,硬盤參與的內存交換顯然降低了效率。是否需要做內存、磁盤的交換,應當由容器內的系統決定,而不是由宿主機。
操作上首先關閉當前系統的交換功能:
# swapoff -a
隨后把磁盤交換分區也關閉,防止重啟后依然使用交換系統:
# vi /etc/fstab
//將其中交換分區一行使用注釋符屏蔽起來,比如:
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
/dev/mapper/userver--vg-root / ext4 errors=remount-ro 0 1
#/dev/mapper/userver--vg-swap_1 none swap sw 0 0
/dev/fd0 /media/floppy0 auto rw,user,noauto,exec,utf8 0 0
4.安裝Docker和curl工具:
k8s是基於容器的生產級容器編排、管理工具。當前已經支持多款容器產品,不僅僅是Docker。但當前從行業中看,使用Docker作為基本的容器管理工具還是主流。
# apt-get update
// ... 輸出略
# apt-get install -y docker.io curl
// ... 輸出略
這里安裝的是Ubuntu內置源的Docker,上一小結說過,通常這個版本並不高。但版本高並不一定是好事,大多情況下用戶最多的版本往往擁有最好的穩定性。希望安裝更新版本Docker的可以參考相關文檔使用Docker的官方源安裝。
步驟二:安裝kubeadm等k8s基本命令行工具:
//安裝k8s軟件倉庫的簽名
# curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
注意從這一步開始,我們使用了國內的阿里雲鏡像服務器,嘗試過不少鏡像倉庫,阿里雲的鏡像質量還是最好的。一直以來,國內的k8s嘗鮮者最大的困難,不是學習、使用k8s,而是獲取k8s的軟件映像。誰讓k8s是Google團隊作品呢:(。在此對阿里雲鏡像團隊的幫助表示致敬,建一鏡像,勝造七級浮屠。
//添加k8s阿里雲軟件鏡像倉庫
# cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
EOF
//更新倉庫索引,讓阿里雲鏡像倉庫生效
# apt update
//安裝3個基本的命令行組件
# apt install -y kubelet kubeadm kubectl
//設置這三個組件不自動更新,如前所述,穩定才更重要
# apt-mark hold kubelet kubeadm kubectl
步驟三:
使用kubeadm集群管理工具自動安裝集群主機:
//參數表示:
//使用阿里鏡像倉庫,安裝版本v1.13.4(成文時最新的版本)
//設置pod工作的網段
kubeadm init --image-repository registry.aliyuncs.com/google_containers --kubernetes-version v1.13.4 --pod-network-cidr=192.168.0.0/16
因為使用了國內的鏡像倉庫,網絡環境沒問題的話,通常幾分鍾就能完成安裝。屏幕上會有比較長的輸出,如果中間安裝中斷,請根據出錯信息排查問題。一直到最后,會出現類似如下的信息:
...略...
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of machines by running the following on each node
as root:
kubeadm join 10.96.200.150:6443 --token zh248e.ivkr6ui3oboxzlji --discovery-token-ca-cert-hash sha256:5e67092d5979a189e0dbb9265a616e62a25d949205d208630933d1cc101dc039
請將上面這部分信息拷貝到記事本留存下來,以后對集群的配置管理會用到。
如果安裝完成后,需要對主機的設置進行修改,比如修改IP地址、主機名稱等,需要對k8s的多個設置文件進行修改。如果修改內容多的話,可能往往不如你刪除當前集群,重新進行配置。
這時候可以使用如下命令:
kubeadm reset
千萬注意,一個正常使用的集群千萬不要使用這個命令,剛才說了,這等於刪除當前集群:)
接着設置用戶的操作環境。不同於Docker必須使用root執行,其實這也是一直被很多用戶詬病的。root操作的隱患有很多,操作不當造成的損失是一方面,因為容器的特征,事實上在宿主機中使用root用戶查看當前進程,所有容器中的程序都是無所遁形,這在安全性上也是無法接受的。
k8s部署集群的時候當然必須使用root,但投產后開始運維,允許使用普通用戶操作集群。
設置方法也很簡單,就是集群初始化時候最后輸出信息的部分:
//退出root狀態到普通操作用戶
# exit
$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
此時,在這個用戶下,已經可以做基本的k8s操作了,我們來看一下當前有哪些Pods運行着。當然,這些都是k8s本身的,我們自己還沒有運行任何Pod。默認情況下k8s自己的Pods是不會被列出的,所以我們這里使用了--all-namespaces參數:
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-78d4cf999f-6pgfr 0/1 Pending 0 87s
kube-system coredns-78d4cf999f-m9kgs 0/1 Pending 0 87s
kube-system etcd-master 1/1 Running 0 47s
kube-system kube-apiserver-master 1/1 Running 0 38s
kube-system kube-controller-manager-master 1/1 Running 0 55s
kube-system kube-proxy-mkg24 1/1 Running 0 87s
kube-system kube-scheduler-master 1/1 Running 0 41s
列表中是組成k8s的所有組件,希望詳細了解k8s內部機理的同學可以按圖索驥瀏覽詳細的文檔。
注意最上面的兩個CoreDNS尚處於Pending狀態。這是因為我們還沒有配置網絡插件。
步驟四:安裝網絡插件。
前面說過,因為容器技術本身並不包含網絡功能。所以社區中自發的為容器開發了多種插件,其中能夠被k8s支持的,在前面給出的鏈接中已經有詳細介紹。可以根據自己的習慣選用。這里的安裝使用Calico。
功能上,Calico會為每個容器分配一個IP地址,這個地址段是我們前面初始化集群的時候給定的,也就是:--pod-network-cidr=192.168.0.0/16。
每一個k8s節點相當於一個路由器,把不同nodes中的容器連接起來。
安裝方法如下:
kubectl apply -f http://mirror.faasx.com/k8s/calico/v3.3.2/rbac-kdd.yaml
kubectl apply -f http://mirror.faasx.com/k8s/calico/v3.3.2/calico.yaml
因為網絡原因,我們同樣適用了國內鏡像,這樣速度會非常快。
注意從一步開始,我們已經可以使用普通的操作用戶來操作k8s集群。而這兩條命令,實際就是執行兩個Pod,這兩個Pod就賦予了容器網絡功能。
這種軟件的分發、配置、使用的方法,跟以往的傳統虛擬化時代有了本質的不同。
一個大的軟件,也會分解成多個小的容器來組裝而成,相信你對“微服務”也有了更多感覺。
稍等幾十秒,讓Calico運行起來,我們可以再查看一下容器的狀態:
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-node-x96gn 2/2 Running 0 47s
kube-system coredns-78d4cf999f-6pgfr 1/1 Running 0 54m
kube-system coredns-78d4cf999f-m9kgs 1/1 Running 0 54m
kube-system etcd-master 1/1 Running 3 53m
kube-system kube-apiserver-master 1/1 Running 3 53m
kube-system kube-controller-manager-master 1/1 Running 3 53m
kube-system kube-proxy-mkg24 1/1 Running 2 54m
kube-system kube-scheduler-master 1/1 Running 3 53m
可以看到,最上面增加了一個calico的Pod,兩個CoreDNS的Pods也執行起來了。
至此一個基本主控節點的k8s已經部署完成。
步驟五:添加工作節點。
工作節點可以有多個,我們這里只演示一個。首先參考前面步驟一和步驟二,如同Master節點一樣,完成前兩步的各項准備工作。
隨后執行如下命令:
//執行拷貝自Master節點初始化的時候輸出信息的最后一行
kubeadm join 10.96.200.150:6443 --token zh248e.ivkr6ui3oboxzlji --discovery-token-ca-cert-hash sha256:5e67092d5979a189e0dbb9265a616e62a25d949205d208630933d1cc101dc039
...略去的輸出...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the master to see this node join the cluster.
只需要執行這一行命令,就可以完成把工作節點加入到集群。命令的輸出信息中,如果顯示上面相同的最后幾行,則表示加入集群成功了。
上面命令的原型如下:
kubeadm join --token <token> <master-ip>:<master-port> --discovery-token-ca-cert-hash sha256:<hash>
其中token和hash值都是由Master節點生成的,如果最初忘記了備份這些信息,都需要到Master節點上去獲取:
//在Master節點上執行
//獲取token值
$ kubeadm token list
zh248e.ivkr6ui3oboxzlji
//獲取hash值
$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
5e67092d5979a189e0dbb9265a616e62a25d949205d208630933d1cc101dc039
token是有時間限制的,默認為24小時。如果超過時限加入工作節點,需要重新生成token:
//在Master節點執行
$ kubeadm token create
現在可以到主控節點上驗證一下集群的工作情況:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
userver-master Ready master 17m v1.13.4
userver-node1 Ready <none> 15m v1.13.4
狀態都是Ready,表示集群正常工作。注意工作節點加入集群需要一點時間,這跟服務器的運行速度有關,所以有可能需要稍等片刻工作節點才會在列表中出現。
步驟六:其它可選配置。
1.默認安裝情況下,主控節點是不參與容器執行,只進行集群管理工作的。這在小的集群中,可能有浪費資源之嫌。這種情況下,可以取消Master節點上的“不可靠”標記,從而主控節點也能參與容器的部署運行。
注意“不可靠”標記是k8s的一種機制,用於標記運行中出現故障的節點,避免新的任務分配到故障節點上去。在這里當然不是指主控節點也故障了,而是不希望主控節點承擔容器的工作,專注於對集群的管理。
$ kubectl taint nodes --all node-role.kubernetes.io/master-
node/userver-master untainted
error: taint "node-role.kubernetes.io/master:" not found
上面的執行結果中,第一行是去掉了主控節點的taint標記,第二行是在userver-node1上執行沒有找到這個標記,不用去除,所以這個報錯是正常的,不用理會。
使用這種方式,也可以在單獨一台服務器上體驗完整的k8s。Win/Mac版本的桌面版Docker內置了k8s,也是采用了類似的方式在一台電腦上提供出來完整的k8s實驗環境。
2.安裝Dashboard。
Dashboard是k8s的Web界面的UI系統。這個組件只是為了降低使用難度,提升用戶友好度。但是從功能上,並不是必須的。喜歡使用鼠標操作的用戶可以自己安裝。
Dashboard是以Pod的形式發布的,所以所謂安裝,其實就是執行一個Pod,這根前面安裝的網絡插件方式一樣。官方提供了配置文件bernetes-dashboard.yaml可以直接運行。但是由於國內網絡的問題和復雜的權限問題,我建議使用修改版。
首先下載阿里雲的鏡像版本的配置文件:
$ curl -O https://raw.githubusercontent.com/AliyunContainerService/k8s-for-docker-desktop/master/kubernetes-dashboard.yaml
//下載后編輯文件,做我們需要的修改
$ vi kubernetes-dashboard.yaml
//文件的最后,服務設置部分,替換成下面的內容:
# ------------------- Dashboard Service ------------------- #
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kube-system
spec:
ports:
- port: 443
targetPort: 8443
nodePort: 32767
selector:
k8s-app: kubernetes-dashboard
type: NodePort
修改的內容,是將原本只能本機訪問的端口映射出來到節點主機的32767端口。
網上大多數資料都是使用kubectl的proxy子命令來做Dashboard服務映射。但問題是大多使用Dashboard的用戶都不會滿足於只在服務器本機使用瀏覽器,何況服務器版本的Ubuntu根本沒有圖形界面。而把端口映射到外面會有很多安全性問題和權限控制問題。所以除非你對k8s非常熟悉,並且有能力自己定制一些安全機制,否則不建議那樣使用。
配置文件修改好了之后,使用如下命令執行:
$ kubectl apply -f kubernetes-dashboard.yaml
如果是第一次運行的話,需要下載映像,阿里雲的鏡像網站還是速度挺快的,應當不用等很久。
繼續使用kubectl get pods --all-namespaces
命令可以查看Pod的執行情況,從而了解Dashboard是否正常運行。
順便提一句,apply執行Pod只需要運行一次。因為復制器的功能,以后重啟系統后,Dashboard也會自動啟動起來。這個在概念部分說過了。
接下來還要為Dashboard建立一個管理員用戶,系統內置的default用戶權限太小,大多信息都無法查詢,所以不建議使用default。
請將下面內容保存到一個配置文件,比如叫dashboard-adminuser.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kube-system
然后在集群執行:
$ kubectl apply -f dashboard-adminuser.yaml
//用戶建立后,獲取admin用戶的Token
$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
//將下面輸出的token部分拷貝備份起來,作為將來的登錄使用
Name: admin-user-token-snppt
Namespace: kube-system
Labels: <none>
Annotations: kubernetes.io/service-account.name: admin-user
kubernetes.io/service-account.uid: 6ec3f9a0-4796-11e9-ade6-000c29dfacdd
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1025 bytes
namespace: 11 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLXNucHB0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI2ZWMzZjlhMC00Nzk2LTExZTktYWRlNi0wMDBjMjlkZmFjZGQiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.eFIzEuGyY_ipi2bLH7tYbDnkjFmDK_sT1uvAmV7dU2mpxgTptpo7tI2qSNmijRcf5LG02ft5rqq5F_IM3jFHrhhwnMG6wz9ccpABR1oqQE8FYxFo8sflETbeUMFCtbm7uPUMI-H4civ6lA_8hZtNKy4VHUUOlAWGDu1EPUV7RBgmX2a2cbcQLlaUWVS_xAG8A5jSHD_LgWM4GEqN5kq2tNCiVNJkf1FHNx9m9x_BJM4ZJCdhKJdVIjnxORG7gUGQeLXr0Q8iRRi0bFqSoAn213Ml2WQbX2RsB0W1ty144EzSj8KeX6uwhN2n2TGsqXJN5S76AArSl4rxKBk9bInkDg
現在使用https協議訪問主控節點IP地址的32767端口就能看到控制台登錄界面了:https://10.96.200.150:32767,因為使用了私有的證書,所以還要確認忽略掉瀏覽器的警告信息窗口。
登錄Dashboard控制台使用Token模式,這個Token就是我們剛才建立用戶的時候所獲得的TOKEN,跟前面加入工作節點所使用token的不是同一個東西。
Dashboard還可以安裝一些酷炫的插件,用於圖形化的顯示系統的負載、使用情況,有興趣可以參考此文:https://iamchuka.com/install-kubernetes-dashboard-part-iii/
至此,集群的安裝全部結束。
k8s基本使用
先從執行一個容器開始,當然這個容器在k8s中是以Pod的形式執行的。下面這條命令,將建立並下載、運行一個nginx容器:
$ kubectl create deployment nginx --image=nginx:alpine
deployment.apps/nginx created
執行完成后,查看執行的Pods列表:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-54458cd494-5g5cq 0/1 ContainerCreating 0 14s
這表示nginx部署已經開始建立,實際上因為第一次運行nginx,這個過程主要是從網上下載映像文件的時間。通常速度都很快,如果你進入root用Docker查看一下nginx映像,你會發現容量只有令人驚訝的16M。
當然,因為我們身處國內,這么小的映像,也有可能無法下載。碰到這種情況你只能自己想想辦法了:)
稍等再次執行查看Pods列表:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-54458cd494-5g5cq 1/1 Running 0 56s
這說明nginx容器已經部署並且執行,並且k8s內置的復制器組件會保證集群中始終有一個nginx容器在執行。類似出現運行中的nginx容器死機了或者崩潰了,k8s的復制器會刪除當前容器並重新執行一個nginx容器從而保證容器數量為1。
比如我們嘗試直接刪除這個Pod:
$ kubectl delete pods nginx-54458cd494-5g5cq
pod "nginx-54458cd494-5g5cq" deleted
再次查看Pods列表:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-54458cd494-4v4v8 1/1 Running 0 6s
可以看到,nginx-54458cd494-5g5cq已經不見了,取而代之的是:nginx-54458cd494-4v4v8。
我們一會兒再學習如何正確刪除Pods,先繼續復制器的話題。
復制器是一個強大的工具,基本透明的貫穿在k8s之中,大多數不需要單獨的操作。Google官方自稱k8s為“生產級別的容器編排系統”,這個編排實際就是復制器的基本能力。而因為有了這種自動編排,以並發換速度;以快速啟動新的容器替代有故障的容器,才是“生產級”的內涵。
下面我們可以操作復制器,把當前執行的nginx容器擴展為3個:
$ kubectl scale deployment nginx --replicas=3
deployment.extensions/nginx scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-54458cd494-4v4v8 1/1 Running 0 10s
nginx-54458cd494-5g5cq 1/1 Running 0 4m15s
nginx-54458cd494-mxmqq 1/1 Running 0 10s
列表中,原有的容器還在,另外增加了2個,共3個Pods在運行中。
這里強調一下,k8s為大規模的並發提供了運維手段。但軟件的研發需要相應的從設計伊始就將並發的支持貫穿在開發中。比如多台服務器間如何同步數據、共享session等。否則同時並發大量的服務器不僅不能提高系統的負載能力,反而可能造成數據的混亂或者系統的互鎖。
kubectl的get子命令是獲取k8s各項信息很常用的工具,需要認真掌握。比如我們讓列表顯示更多信息:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-54458cd494-4v4v8 1/1 Running 0 76s 192.168.1.5 userver-node1 <none> <none>
nginx-54458cd494-5g5cq 1/1 Running 0 5m21s 192.168.1.4 userver-node1 <none> <none>
nginx-54458cd494-mxmqq 1/1 Running 0 76s 192.168.0.10 userver-master <none> <none>
在生產環境中,當運行的Pods比較多的時候,我們還需要對列表進行過濾。不然你根本找不到你需要的Pod。比如使用app的名稱進行過濾:
//使用了過濾器,當然這里跟上面執行結果是一樣的,因為我們沒有運行別的Pods
$ kubectl get pods -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-54458cd494-4v4v8 1/1 Running 0 76s 192.168.1.5 userver-node1 <none> <none>
nginx-54458cd494-5g5cq 1/1 Running 0 5m21s 192.168.1.4 userver-node1 <none> <none>
nginx-54458cd494-mxmqq 1/1 Running 0 76s 192.168.0.10 userver-master <none> <none>
k8s通過etcd組件支持使用key/value對兒的標簽體系和標簽過濾來精確定位Pods,從而降低操作復雜性。比如刪除Pods的時候,也可以使用這種標簽過濾的方式選擇多個刪除對象,:
$ kubectl delete pods -l app=nginx
pod "nginx-54458cd494-4v4v8" deleted
pod "nginx-54458cd494-5g5cq" deleted
pod "nginx-54458cd494-mxmqq" deleted
當然了,因為復制器的存在,當前的三個容器被刪除,復制器會自動啟動3個新的容器,來保證nginx容器總數為3:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-54458cd494-cjcjr 1/1 Running 0 20s 192.168.0.11 userver-master <none> <none>
nginx-54458cd494-ncz4n 1/1 Running 0 20s 192.168.1.7 userver-node1 <none> <none>
nginx-54458cd494-vjzb6 1/1 Running 0 20s 192.168.1.6 userver-node1 <none> <none>
可以分別測試三個Pods的工作情況:
$ curl 192.168.0.11:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ curl 192.168.1.7:80
...
//80端口是默認的http端口,可以省略
$ curl 192.168.1.6
...
現在三個容器都正常工作了,但訪問都是通過的內網,如果想在外網訪問,就需要配置k8s的端口服務,把端口暴露出來:
$ kubectl expose deployment nginx --port=80 --type=NodePort
service/nginx exposed
service的概念我們前面講過,這里實際上就是建立了一個服務,類型是NodePort。把容器的80端口開放出來,到一個隨機的端口號,端口號的范圍默認是30000-32767可以修改。端口的映射是由kube-proxy完成的,因為kube-proxy在每台節點服務器上都會運行,所以訪問任一節點的IP地址,配合這個指定的端口號,就可以訪問到nginx的輸出,這種模式就叫NodePort。kube-proxy的映射功能同時包含了負載均衡的流量分配能力。並且因為服務的建立就是映射到了指定的Pods群,也無需再次進行服務發現。
使用下面命令查看這個服務的具體信息,其中有端口號:
$ kubectl get services nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.106.91.63 <none> 80:31739/TCP 2m29s
上面信息表明,對外映射的端口為31739。其中CLUSTER-IP是在容器內部訪問時候使用的,在外部是訪問不到的。這里的CLUSTER也不是指的k8s集群,而是指是我們啟動了幾個nginx Pods,是運行在k8s集群上的nginx的集群,這一點一定不要誤會。之所以單獨給出一個獨立的內部IP地址,是因為在開發中,內部的服務訪問同樣也需要負載均衡和隱含其中的服務發現。
我們測試一下:
$ curl 10.96.200.150:31739
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ curl 10.96.200.160:31739
...
在實際應用中,訪問任意一台節點的對應端口都是可以的。端口服務建立的時候,也可以指定只捆綁某個IP地址的端口,請參考命令的幫助文檔。
下面我們再開一個Pod,看看容器內部對其它容器的訪問。
這次我們開一個busybox的容器,以便使用bash shell操作:
$ kubectl run -it --generator=run-pod/v1 curl --image=radial/busyboxplus:curl
[ root@curl-66959f6557-wpp5p:/ ]$
因為我們希望使用互動終端的方式(參數-it)執行Pod,所以直接使用kubectl的run子命令。
執行之后,如果是第一次使用busybox,需要下載映像,所以要有片刻的等待。當然速度也會很快,因為這個映像只有4M多的容量。
隨后出現的提示符,已經是容器中的shell提示符。
在其中執行:
[ root@curl-66959f6557-wpp5p:/ ]$ nslookup nginx.default.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx.default.svc.cluster.local
Address 1: 10.106.91.63 nginx.default.svc.cluster.local
[ root@curl-66959f6557-wpp5p:/ ]$ curl nginx.default.svc.cluster.local
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
注意名稱:nginx.default.svc.cluster.local是我們剛才建立的端口服務的全稱。也就是nginx這個名字,加上.default.svc.cluster.local后綴。得到的IP地址,你會發現,跟上面的CLUSTER-IP地址是完全相同的。
因為無論是CLUSTER-IP,還是每台容器的地址,都是動態變化的。所以在編程中,應當堅持使用nginx.default.svc.cluster.local這樣的名稱來訪問其它的服務,就像上例中curl獲取nginx首頁做的那樣。這樣做一是保證程序的可遷移性,也是為了利用系統內置的負載均衡機制。
Pod一旦建立,不管是Pod崩潰退出,還是集群重啟,都會自動重新運行。並且會保證數量跟復制器設定的數量吻合。
所以當容器不再需要的時候,我們需要顯式的刪除,方法如下:
//刪除對外的端口服務
$ kubectl delete services nginx
service "nginx" deleted
//刪除部署
$ kubectl delete deployment nginx
deployment.extensions "nginx" deleted
$ kubectl delete deployment curl
deployment.extensions "curl" deleted
//一些容器的停止需要時間,所以稍等再執行...
$ kubectl get pods
No resources found.
上面介紹的,是從使用VMWare起步,到Docker,再延伸而來的習慣,是基於映像文件的使用方法。k8s的魅力遠非止步於此。
我們使用中可能有很多感受,就是用這種方式使用容器,命令行冗長,不容易記憶,更不方便修改和定制,對於寫入腳本重復使用也不方便。
k8s引入了使用參數文件來表述容器的各方面配置,易讀易用。官方推薦的是yaml文件。
在上一小節介紹安裝的時候說過,Ubuntu新版本中也把yaml格式作為部分配置文件的標准。yaml算得上繼json之后最新一代的結構化文檔格式了。yaml文件有幾個簡單的須知:
- 大小寫敏感
- 使用縮進表示層級關系
- 縮進時不允許使用Tab鍵,只允許使用空格。
- 縮進的空格數目不重要,只要相同層級的元素左側對齊即可
表示注釋,從這個字符一直到行尾,都會被解析器忽略。
k8s也支持json格式的配置文件,只是網上大多數存在的資源都已經使用了yaml,所以建議你也從yaml開始,跟社區保持相同。
在官方的文檔中,編寫配置文件的說明並不是yaml使用說明,而是“使用Deployment運行應用”,路徑為:https://kubernetes.io/zh/docs/tasks/run-application/run-stateless-application-deployment/,一定要仔細閱讀。
作為串講,這里舉一個小例子。首先列出yaml文件:
---
apiVersion: v1
kind: Pod
metadata:
name: web-site
labels:
app: web
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
- name: helloecho
image: hashicorp/http-echo
args:
- "-text=hello"
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: web-site-svc
spec:
type: NodePort
ports:
- name: nginx-port
port: 80
targetPort: 80
- name: helloecho-port
port: 88
targetPort: 5678
selector:
app: web
使用“---”分割線隔開,這個文件中包含了兩個k8s任務,一個是定義Pod,一個是定義對外暴露端口的服務。每個yaml文件中,包含一個任務就可以。但為了便於管理,完成一個工作的多個任務習慣寫在一起,這樣一次執行就能完成全部工作,並且不用擔心執行的順序。
apiVersion一行指的是k8s的接口版本。yaml實際相當於根據k8s的接口規范來完成任務。
kind指定任務類型,這里分別演示了Pod和Service。
metadata部分是任務相關的key/value對兒,name是必須的,最終就是Pod和Service的名字。labels下面的則是自己定義的,自己定義的目的是為了將來對相關對象的檢索。比如Service定義暴露端口的時候,使用selector指令使用app檢索為:web的Pod。
這是一個典型的一個Pod中包含多個容器的示例。下面的spec中是定義的主要部分,我覺得不用多解釋應當能看懂。這里使用了一個標准的nginx,一個http協議的echo小工具。echo工具的定義里面,使用args指令指定參數“-text=hello”來定制一個“hello”的字符串反饋信息。很多簡單系統里面,會用這個小工具做健康檢查的信息反饋。
服務的定義里面,使用NodePort模式(看我們前面的例子)來暴露兩個對外服務端口,分別映射到nginx和echo兩個容器。
上面的文件保存到一個yaml文本文件,比如叫:web-test.yaml。隨后可以執行這個文件:
//執行配置文件,建立Pod和Service
$ kubectl apply -f web-test.yaml
pod/web-site created
service/web-site-svc created
//查看Pod
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
web-site 2/2 Running 0 14s
//查看服務
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31d
web-site-svc NodePort 10.104.214.83 <none> 80:31686/TCP,88:30298/TCP 7s
//測試nginx
$ curl 127.0.0.1:31686
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
//測試echo
$ curl 127.0.0.1:30298
hello
如果有root權限,還可以進一步使用docker ps來檢查一下容器運行的情況。可以看到同一個Pod中的nginx跟helloecho分別是兩個不同的容器。這里就略掉了。
如果Pod沒有像預期的一樣執行,使用kubectl get pods命令查看pods列表的時候會看到狀態不是Runing。如果仍然無法判斷錯誤來源,使用命令kubectl describe pods web-site能夠看到同Pod相關的所有詳細信息,其中包括出錯的信息,可以幫助你判斷未能啟動的原因。
測試過程結束后,依靠配置文件刪除這個應用也變得簡單了:
$ kubectl delete -f web-test.yaml
pod "web-site" deleted
service "web-site-svc" deleted
你可能還有一個疑問,這兩個容器之間,似乎什么關系也沒有啊,為什么要放到一個Pod中?的確如此,一方面這里僅僅是個演示,簡單介紹如何使用yaml配置文件自動化部署的過程。再者,從運維端看,運維的目的就是把各部分容器正常運行起來,時刻讓其保持在健康的狀態,及時處理各類異常事件。
從某一個容器中,如何訪問另外一個容器的計算結果,並進行有機的組合和相關處理,是軟件開發過程中解決的問題,運維人員基本沒有可能去干涉。
在k8s集群的運維過程中,Docker容器層產生的過期容器及不再使用的鏡像,會由Image Collection
及Container Collection自動回收,正常使用情況下,並不需要運維人員手工刪除。
作為全面操控k8s的配置文件,這算的上最簡單的例子。還有很多指令需要學習,請盡快閱讀相關文檔。參考別人的使用示例也是快速學習的一種手段,推薦一個使用k8s配置WordPress應用的講解:https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/
一般來說,構建k8s集群中的生產環境,同以前使用傳統虛機的方式並沒有什么不同,通常是這樣的工作流程:
- 整個生產系統,包含幾個部分的軟件模塊
- 根據軟件模塊划分,使用盡可能小的粒度,分別構建Docker映像,並規划服務端口
- 分析這些軟件模塊相互間的關系,確定模塊間共享和溝通數據的方式
- 根據軟件間相關關系建立k8s的存儲對象和網絡對象,並形成yaml配置文件
- 根據軟件模塊相互間的關聯度,考慮高關聯的是否需要放入同一個Pod
- 分析根據隨着運行壓力的增長,軟件模塊需要增加計算量的數量級關系,數量級相關性強的需要考慮是否放置在同一個Pod,以便自動編排時同時增加部署數量
- 根據以上分析結果,手工運行容器進行概念測試
- 根據測試結果,經過修正,構建Pod部署的yaml配置文件
- 構建端口服務配置文件
- 測試運行
整個的過程中,軟件研發部門和運維部門需要高度的配合,共同完成工作。
繼續演進
k8s在企業級運維的占用率快速的增長,然而新的需求繼續出現。
最核心的問題是,k8s的集群系統,是由主控和工作節點兩個部分做成的。小的集群中,只需要一個主控節點。大集群中,k8s提供了高可用的模式(Highly Available Clusters),這種模式支持最少3台主控節點組成主控組,來統一管理整個的k8s集群。
但是即便在這種高可用的模式下,仍然需要配置負載均衡機制,比如HAProxy,放置在控制群組之前,這無可避免的成為了又一個單點瓶頸。
此外還有一些需求,比如對容器的監控功能不足,調試容器的配置問題需要研發團隊配合輸出大量的日志信息。再比如紅黑發布的導流控制等。
在開源社區中,只要有需求,就有大量的團隊為此努力,開發出同樣大量的產品或者插件。在其中,這次是Istio脫穎而出。
簡單來說,Istio采用了比較新的思路來解決問題。最主要的就是,原本每個容器,需要同管理節點之間建立通訊連接,溝通容器狀態及接受控制信息。因為這種機制,管理節點的健康狀況和負載能力,成為了整個系統的瓶頸。
Istio同樣也是在容器上配置自己的組件(Sidecar),但這些控制數據流,不再溝通到某個具體的管理節點,而是所有節點直接互相溝通,組成了一個服務網格(ServiceMesh)。網格中任意一個容器出現問題,並不影響整個網格的信息傳遞和控制,故障的容器同樣也會在服務支持下被刪除,並被新生成的容器替代。系統不再存在單點瓶頸。
Sidecar的核心是代理機制,接管了所有進出容器的通訊流。在這種機制的幫助下,在流量管理、安全性、控制、和監控方面,顯然有了更可靠的手段。因此在Istio首頁(https://istio.io)上,這四個特征成為了主要的賣點:
- 連接(Connect)流量管理:智能控制服務之間的調用流量,能夠實現灰度升級、AB 測試和紅黑部署等功能
- 安全加固(Secure):自動為服務之間的調用提供認證、授權和加密。
- 控制(Control):應用用戶定義的策略,保證資源在消費者中公平分配。
- 可觀察性(Observe):查看服務運行期間的各種數據,比如日志、監控和 tracing,了解服務的運行情況。
應當說單純這些功能,在社區中都有不錯的組件可以完成,為了少量的需求安裝一個輕型的組件也很經濟。實際上Istio也直接或者間接的使用了大量社區貢獻的組件。但有一點一定要明白,雖然安裝同樣的組件能夠完成同樣的功能,但除非使用了Istio的Sidecar機制,否則需求是滿足了,但並沒有解決單點瓶頸問題,並不是ServiceMesh。
(Sidecar本意是指跨斗摩托車邊上的跨斗,這里指正式任務容器旁邊再附着一個管理的容器,組成服務網格。Sidecar用在這里是非常形象的說法。)
Istio的安裝
Istio可以在k8s上直接安裝,這個相愛相殺的世界啊:)
當然這個特征是k8s的市場占有率決定的,支持k8s能讓Istio更易用也有更多用戶。實際上Istio可以在純Docker環境配合Docker Compose安裝,並且在沒有k8s存在的情況下獨立運行。
在k8s集群下安裝Istio,兩個產品的功能都能保留並且和平共處,不失為一種好的解決方案。很多企業也是這樣做的。
依然建議你去官網仔細閱讀文檔https://istio.io/zh/docs/,Istio的中文文檔做的非常棒。 接下來是串講的簡化版本。
Istio也是分為命令行控制工具和容器化的系統兩部分。安裝之前請先確認一下硬件配置,Docker+k8s+Istio所需最小的內存是4G,上一節我們使用2G內存來學習k8s,現在是不夠用的。
在k8s的主控節點上安裝命令行工具及示例等:
$ curl -L https://git.io/getLatestIstio | sh -
getLatestIstio是官方提供的下載腳本,下載后自動執行,完成后,可以把最新版本的CLI安裝於當前目錄。
隨后把Istio的程序目錄加入路徑:
//路徑設置.bashrc,重新登錄生效
echo "export PATH=$PATH:$PWD/istio-1.0.6/bin" >> ~/.profile
//修改當前路徑設置
export PATH=$PATH:$PWD/istio-1.0.6/bin
接着是安裝Istio的主體部分:
//進入到Istio下載目錄,下面所使用的yaml文件都使用了相對路徑
$ cd istio-1.0.6/
//在k8s中自定義資源類型
$ kubectl apply -f install/kubernetes/helm/istio/templates/crds.yaml
customresourcedefinition.apiextensions.k8s.io/virtualservices.networking.istio.io created
customresourcedefinition.apiextensions.k8s.io/destinationrules.networking.istio.io created
customresourcedefinition.apiextensions.k8s.io/serviceentries.networking.istio.io created
customresourcedefinition.apiextensions.k8s.io/gateways.networking.istio.io created
customresourcedefinition.apiextensions.k8s.io/envoyfilters.networking.istio.io created
customresourcedefinition.apiextensions.k8s.io/policies.authentication.istio.io created
customresourcedefinition.apiextensions.k8s.io/meshpolicies.authentication.istio.io created
customresourcedefinition.apiextensions.k8s.io/httpapispecbindings.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/httpapispecs.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/quotaspecbindings.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/quotaspecs.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/rules.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/attributemanifests.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/bypasses.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/circonuses.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/deniers.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/fluentds.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/kubernetesenvs.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/listcheckers.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/memquotas.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/noops.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/opas.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/prometheuses.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/rbacs.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/redisquotas.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/servicecontrols.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/signalfxs.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/solarwindses.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/stackdrivers.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/statsds.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/stdios.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/apikeys.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/authorizations.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/checknothings.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/kuberneteses.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/listentries.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/logentries.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/edges.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/metrics.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/quotas.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/reportnothings.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/servicecontrolreports.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/tracespans.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/rbacconfigs.rbac.istio.io created
customresourcedefinition.apiextensions.k8s.io/serviceroles.rbac.istio.io created
customresourcedefinition.apiextensions.k8s.io/servicerolebindings.rbac.istio.io created
customresourcedefinition.apiextensions.k8s.io/adapters.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/instances.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/templates.config.istio.io created
customresourcedefinition.apiextensions.k8s.io/handlers.config.istio.io created
//安裝基本版本的Istio
$ kubectl apply -f install/kubernetes/istio-demo.yaml
namespace/istio-system created
configmap/istio-galley-configuration created
configmap/istio-grafana-custom-resources created
configmap/istio-grafana-configuration-dashboards created
configmap/istio-grafana created
configmap/istio-statsd-prom-bridge created
configmap/prometheus created
configmap/istio-security-custom-resources created
configmap/istio created
configmap/istio-sidecar-injector created
serviceaccount/istio-galley-service-account created
serviceaccount/istio-egressgateway-service-account created
serviceaccount/istio-ingressgateway-service-account created
serviceaccount/istio-grafana-post-install-account created
clusterrole.rbac.authorization.k8s.io/istio-grafana-post-install-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-grafana-post-install-role-binding-istio-system created
job.batch/istio-grafana-post-install created
serviceaccount/istio-mixer-service-account created
serviceaccount/istio-pilot-service-account created
serviceaccount/prometheus created
serviceaccount/istio-cleanup-secrets-service-account created
clusterrole.rbac.authorization.k8s.io/istio-cleanup-secrets-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-cleanup-secrets-istio-system created
job.batch/istio-cleanup-secrets created
serviceaccount/istio-security-post-install-account created
clusterrole.rbac.authorization.k8s.io/istio-security-post-install-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-security-post-install-role-binding-istio-system created
job.batch/istio-security-post-install created
serviceaccount/istio-citadel-service-account created
serviceaccount/istio-sidecar-injector-service-account created
customresourcedefinition.apiextensions.k8s.io/virtualservices.networking.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/destinationrules.networking.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/serviceentries.networking.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/gateways.networking.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/envoyfilters.networking.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/httpapispecbindings.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/httpapispecs.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/quotaspecbindings.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/quotaspecs.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/rules.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/attributemanifests.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/bypasses.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/circonuses.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/deniers.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/fluentds.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/kubernetesenvs.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/listcheckers.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/memquotas.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/noops.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/opas.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/prometheuses.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/rbacs.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/redisquotas.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/servicecontrols.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/signalfxs.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/solarwindses.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/stackdrivers.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/statsds.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/stdios.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/apikeys.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/authorizations.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/checknothings.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/kuberneteses.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/listentries.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/logentries.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/edges.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/metrics.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/quotas.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/reportnothings.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/servicecontrolreports.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/tracespans.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/rbacconfigs.rbac.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/serviceroles.rbac.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/servicerolebindings.rbac.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/adapters.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/instances.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/templates.config.istio.io unchanged
customresourcedefinition.apiextensions.k8s.io/handlers.config.istio.io unchanged
clusterrole.rbac.authorization.k8s.io/istio-galley-istio-system created
clusterrole.rbac.authorization.k8s.io/istio-egressgateway-istio-system created
clusterrole.rbac.authorization.k8s.io/istio-ingressgateway-istio-system created
clusterrole.rbac.authorization.k8s.io/istio-mixer-istio-system created
clusterrole.rbac.authorization.k8s.io/istio-pilot-istio-system created
clusterrole.rbac.authorization.k8s.io/prometheus-istio-system created
clusterrole.rbac.authorization.k8s.io/istio-citadel-istio-system created
clusterrole.rbac.authorization.k8s.io/istio-sidecar-injector-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-galley-admin-role-binding-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-egressgateway-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-ingressgateway-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-mixer-admin-role-binding-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-pilot-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/prometheus-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-citadel-istio-system created
clusterrolebinding.rbac.authorization.k8s.io/istio-sidecar-injector-admin-role-binding-istio-system created
service/istio-galley created
service/istio-egressgateway created
service/istio-ingressgateway created
service/grafana created
service/istio-policy created
service/istio-telemetry created
service/istio-pilot created
service/prometheus created
service/istio-citadel created
service/servicegraph created
service/istio-sidecar-injector created
deployment.extensions/istio-galley created
deployment.extensions/istio-egressgateway created
deployment.extensions/istio-ingressgateway created
deployment.extensions/grafana created
deployment.extensions/istio-policy created
deployment.extensions/istio-telemetry created
deployment.extensions/istio-pilot created
deployment.extensions/prometheus created
deployment.extensions/istio-citadel created
deployment.extensions/servicegraph created
deployment.extensions/istio-sidecar-injector created
deployment.extensions/istio-tracing created
gateway.networking.istio.io/istio-autogenerated-k8s-ingress created
horizontalpodautoscaler.autoscaling/istio-egressgateway created
horizontalpodautoscaler.autoscaling/istio-ingressgateway created
horizontalpodautoscaler.autoscaling/istio-policy created
horizontalpodautoscaler.autoscaling/istio-telemetry created
horizontalpodautoscaler.autoscaling/istio-pilot created
service/jaeger-query created
service/jaeger-collector created
service/jaeger-agent created
service/zipkin created
service/tracing created
mutatingwebhookconfiguration.admissionregistration.k8s.io/istio-sidecar-injector created
attributemanifest.config.istio.io/istioproxy created
attributemanifest.config.istio.io/kubernetes created
stdio.config.istio.io/handler created
logentry.config.istio.io/accesslog created
logentry.config.istio.io/tcpaccesslog created
rule.config.istio.io/stdio created
rule.config.istio.io/stdiotcp created
metric.config.istio.io/requestcount created
metric.config.istio.io/requestduration created
metric.config.istio.io/requestsize created
metric.config.istio.io/responsesize created
metric.config.istio.io/tcpbytesent created
metric.config.istio.io/tcpbytereceived created
prometheus.config.istio.io/handler created
rule.config.istio.io/promhttp created
rule.config.istio.io/promtcp created
kubernetesenv.config.istio.io/handler created
rule.config.istio.io/kubeattrgenrulerule created
rule.config.istio.io/tcpkubeattrgenrulerule created
kubernetes.config.istio.io/attributes created
destinationrule.networking.istio.io/istio-policy created
destinationrule.networking.istio.io/istio-telemetry created
//驗證一下安裝的服務
$ kubectl get svc -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana ClusterIP 10.99.63.62 <none> 3000/TCP 104s
istio-citadel ClusterIP 10.97.149.61 <none> 8060/TCP,9093/TCP 103s
istio-egressgateway ClusterIP 10.98.244.253 <none> 80/TCP,443/TCP 104s
istio-galley ClusterIP 10.105.199.110 <none> 443/TCP,9093/TCP 105s
istio-ingressgateway LoadBalancer 10.109.153.221 <pending> 80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:30607/TCP,8060:31625/TCP,853:30655/TCP,15030:32494/TCP,15031:31070/TCP 104s
istio-pilot ClusterIP 10.108.220.116 <none> 15010/TCP,15011/TCP,8080/TCP,9093/TCP 104s
istio-policy ClusterIP 10.101.97.253 <none> 9091/TCP,15004/TCP,9093/TCP 104s
istio-sidecar-injector ClusterIP 10.103.63.166 <none> 443/TCP 103s
istio-telemetry ClusterIP 10.107.184.81 <none> 9091/TCP,15004/TCP,9093/TCP,42422/TCP 104s
jaeger-agent ClusterIP None <none> 5775/UDP,6831/UDP,6832/UDP 101s
jaeger-collector ClusterIP 10.108.136.200 <none> 14267/TCP,14268/TCP 101s
jaeger-query ClusterIP 10.105.46.4 <none> 16686/TCP 101s
prometheus ClusterIP 10.102.94.141 <none> 9090/TCP 103s
servicegraph ClusterIP 10.96.220.16 <none> 8088/TCP 103s
tracing ClusterIP 10.105.46.106 <none> 80/TCP 101s
zipkin ClusterIP 10.106.153.194 <none> 9411/TCP 101s
//驗證執行的Pods:
$ kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
grafana-59b8896965-vlbd5 1/1 Running 0 89s
istio-citadel-6f444d9999-cz6gk 1/1 Running 0 85s
istio-cleanup-secrets-29mcx 0/1 Completed 0 91s
istio-egressgateway-6d79447874-gzssf 1/1 Running 0 89s
istio-galley-685bb48846-wx8q4 1/1 Running 0 89s
istio-grafana-post-install-gkz6g 0/1 Completed 0 91s
istio-ingressgateway-5b64fffc9f-f8h2q 1/1 Running 0 89s
istio-pilot-7f558fc848-jtrqs 2/2 Running 0 88s
istio-policy-547d64b8d7-br5cn 2/2 Running 0 88s
istio-security-post-install-xz5xd 0/1 Completed 0 91s
istio-sidecar-injector-5d8dd9448d-m4lls 1/1 Running 0 83s
istio-telemetry-c5488fc49-g5vjw 2/2 Running 0 88s
istio-tracing-6b994895fd-6hwqv 1/1 Running 0 82s
prometheus-76b7745b64-gpdkw 1/1 Running 0 88s
servicegraph-cb9b94c-7sjks 1/1 Running 0 83s
從Pods狀態中,如果有不是Running的,可以使用kubectl describe子命令查看詳細狀態,並找出錯誤原因。例如:kubectl -n istio-system describe pods istio-pilot。
基礎軟硬件環境在安裝k8s的時候等於經過了驗證,所以通常碰到的問題都是因為網絡原因映像無法下載。Completed狀態的一般是依賴包未能運行導致的退出,這種會自動嘗試重啟。或者是只需要安裝時執行一次的容器。正常情況都不需要管。
Istio可能是發布時間尚短,國內沒有能找到比較好的鏡像,所以下載比較慢。我整理了一下需要的鏡像列表,建議網絡環境不理想的,在正式安裝Istio之前,先使用root權限在Docker中完成鏡像的下載,然后再執行上述安裝。
docker pull docker.io/istio/proxy_init:1.0.6
docker pull docker.io/istio/proxyv2:1.0.6
docker pull quay.io/coreos/hyperkube:v1.7.6_coreos.0
docker pull docker.io/istio/galley:1.0.6
docker pull grafana/grafana:5.2.3
docker pull docker.io/istio/mixer:1.0.6
docker pull docker.io/istio/pilot:1.0.6
docker pull docker.io/prom/prometheus:v2.3.1
docker pull docker.io/istio/citadel:1.0.6
docker pull docker.io/istio/servicegraph:1.0.6
docker pull docker.io/istio/sidecar_injector:1.0.6
docker pull docker.io/jaegertracing/all-in-one:1.5
Istio的安裝就這樣兩步,使用容器以來,軟件的安裝和配置變得越來越便利。
//順便,在k8s環境中安裝Istio之后,嘗試列表所有的系統級Pods,也是很壯觀的一坨
//不過在微服務的理念下,其實一個容器往往就是一個進程,總體耗費資源並不多
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
istio-system grafana-59b8896965-vlbd5 1/1 Running 2 17h
istio-system istio-citadel-6f444d9999-cz6gk 1/1 Running 2 17h
istio-system istio-egressgateway-6d79447874-gzssf 1/1 Running 2 17h
istio-system istio-galley-685bb48846-4f9qq 1/1 Running 1 16m
istio-system istio-ingressgateway-5b64fffc9f-lqjjv 1/1 Running 1 16m
istio-system istio-pilot-7f558fc848-mts8g 2/2 Running 2 16m
istio-system istio-policy-547d64b8d7-br5cn 2/2 Running 4 17h
istio-system istio-sidecar-injector-5d8dd9448d-ftsrr 1/1 Running 1 16m
istio-system istio-telemetry-c5488fc49-gpdx5 2/2 Running 2 16m
istio-system istio-tracing-6b994895fd-6hwqv 1/1 Running 2 17h
istio-system prometheus-76b7745b64-gpdkw 1/1 Running 2 17h
istio-system servicegraph-cb9b94c-7sjks 1/1 Running 4 17h
kube-system calico-node-sjdcg 2/2 Running 24 5d21h
kube-system coredns-78d4cf999f-pg4mn 1/1 Running 12 5d21h
kube-system coredns-78d4cf999f-zsh76 1/1 Running 12 5d21h
kube-system etcd-userver-master 1/1 Running 12 5d21h
kube-system kube-apiserver-userver-master 1/1 Running 13 5d21h
kube-system kube-controller-manager-userver-master 1/1 Running 15 5d21h
kube-system kube-proxy-fx42f 1/1 Running 12 5d21h
kube-system kube-scheduler-userver-master 1/1 Running 14 5d21h
kube-system kubernetes-dashboard-57df4db6b-5vmqk 1/1 Running 1 16m
Istio基本使用
上面說過,Istio的基本原理是在每個Pods中增加Sidecar來完成對Pods所有數據流量的代理,並以此來達成各項附加的功能。
所以在正式任務之前,我們先做一個設置,讓k8s每次啟動容器的時候,自動完成Istio Sidercar的注入,這樣一來,我們原有的Pods配置文件,無需修改,自動就會得到Istio提供的各項功能:
kubectl label namespace default istio-injection=enabled
這一行就是為默認的default命名空間增加istio sidecard注入功能。如果希望為其它命名空間添加自動注入,把上面命令中的default換成你希望的命名空間的名字。
查看哪些命名空間打開了自動注入,可以使用命令:
kubectl get namespace -L istio-injection
單純從命令行角度來看,使用Istio同使用基本的k8s似乎沒有什么區別。
實際上Istio也是使用配置文件來啟動容器。Istio在配置文件中,提供了大量自己特有的配置指令,從而提供服務網格的特征和功能。
作為對比,我們來看一個使用Istio啟動容器的配置文件實例:
apiVersion: v1
kind: Service
metadata:
name: istio-dog
labels:
app: istio-dog
service: istio-dog
spec:
ports:
- port: 5678
name: http
selector:
app: istio-dog
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: istio-dog-v1
labels:
app: istio-dog
version: v1
spec:
replicas: 1
template:
metadata:
labels:
app: istio-dog
version: v1
spec:
containers:
- name: istio-dog
image: hashicorp/http-echo
args:
- "-text=istio-dog-v1"
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5678
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: istio-dog-v2
labels:
app: istio-dog
version: v2
spec:
replicas: 1
template:
metadata:
labels:
app: istio-dog
version: v2
spec:
containers:
- name: istio-dog
image: hashicorp/http-echo
args:
- "-text=istio-dog-v2"
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
name: istio-cat
labels:
app: istio-cat
service: istio-cat
spec:
ports:
- port: 5678
name: http
selector:
app: istio-cat
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: istio-cat-v1
labels:
app: istio-cat
version: v1
spec:
replicas: 1
template:
metadata:
labels:
app: istio-cat
version: v1
spec:
containers:
- name: istio-cat
image: hashicorp/http-echo
args:
- "-text=istio-cat-v1"
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5678
Istio官方提供了Bookinfo示例作為入門教程,所有配套文檔均有中文版,示例和文檔寫的都很棒。站在運維人員角度看,Bookinfo示例略顯門檻偏高,所以我使用自己寫的示例來演示、入門。
上面的配置文件中,定義了兩個服務和三個Pod,其中istio-dog定義了兩個版本。版本化是A/B測試和紅黑發布的必須功能,也是我們前面說過的k8s的弱項。
Pod定義的部分,使用不一樣的類型Deployment。實際上區別同Pod類型並不大,看起來應當不困難。服務部分,單純從指令上跟前面k8s的示例沒有區別,但是我們並沒有定義服務類型比如NodePort。這是因為,我們使用的是Istio服務網格,雖然運行在k8s環境下,但並不需要k8s的網絡代理功能。
前面說過,智能的流量控制是Istio第一大基本功能。理解並使用這一功能,需要先介紹一下做Istio流量控制的主要路徑:
- 用戶的訪問,首先到達Istio的網關Gateway。Gateway配置4-6層的邊緣接口,比如端口、TLS。這個配置為整個系統提供入口流量。
- 從Gateway出來,流量到達Istio的虛擬服務VirtualService。每一個VirtualService都必須綁定到一個Gateway來獲取入口流量。虛擬服務根據配置的各項條件,在服務Service之間分拆流量。或者說,到達虛擬服務的流量,根據配置的各項條件,到達不同的服務。這里服務Service就是前面講過的k8s服務,雖然是由Istio管理的,但是同一個東西。
- 服務相當於基本的負載均衡機制,對應到一組Pods的對外端口。這個同k8s相同。
- 流量從VirtualService到達Service之間,可以設置目標規則DestinationRule。目標規則在VirtualService之后生效。可以包含斷路器、子版本特定規則、負載均衡策略、流量策略等更精細的控制手段。
- Service Entry:Istio 內部會維護一個服務注冊表,可以用 ServiceEntry 向其中加入額外的條目。通常這個對象用來啟用對 Istio 服務網格之外的服務發出請求。例如可以用來允許對 *.foo.com 域名上的服務主機的調用。
了解了這個機制,我們看一個網絡設置的示例:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-test-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-test
spec:
hosts:
- "*"
gateways:
- istio-test-gateway
http:
- match:
- uri:
exact: /cat-and-dog
route:
- destination:
host: istio-dog
port:
number: 5678
配置文件中首先定義了一個Gateway,端口號80,特別注意一下其中的protocol指令,定義了HTTP協議。這一點跟k8s是有比較大的區別的,雖然你可能認為是理所當然的。原因是Istio是使用Sidecar全面代理作為核心來實現所有功能的,既然是代理,並且需要流經的數據進行解析,所以必然對可識別的協議類型是有限制的。這一點同k8s的端口映射實際有根本的區別。
配置文件中又定義了一個虛擬服務,從Gateway過來的流量中,如果路徑匹配/cat-and-dog,則將流量導入到istio-dog服務。注意host容易讓人誤解,這里實際就是Service名,k8s中也是一樣的。
我們把兩個配置文件,分別保存為比如istio-test.yaml和istio-test-gateway1.yaml,然后執行來測試一下。
$ kubectl apply -f istio-test.yaml
service/istio-dog created
deployment.extensions/istio-dog-v1 created
deployment.extensions/istio-dog-v2 created
service/istio-cat created
deployment.extensions/istio-cat-v1 created
$ kubectl apply -f istio-test-gateway1.yaml
gateway.networking.istio.io/istio-test-gateway created
virtualservice.networking.istio.io/istio-test created
$ kubectl get VirtualServices
NAME AGE
istio-test 25s
$ kubectl get Gateways
NAME AGE
istio-test-gateway 3m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
istio-cat-5fdcfd9fcd-bncmm 2/2 Running 0 3m57s
istio-dog-v1-657d697cd8-pdm7w 2/2 Running 0 3m57s
istio-dog-v2-69f6bb9dd8-wx8f7 2/2 Running 0 3m57s
//注意運行在Istio中的服務並不需要導出端口
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-cat ClusterIP 10.106.214.198 <none> 80/TCP 4m24s
istio-dog ClusterIP 10.106.22.195 <none> 80/TCP 4m24s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 6d
不同於k8s,Istio已經預先導出了80/443端口,這是由istio-ingressgateway服務來完成的:
$ kubectl get svc istio-ingressgateway -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.98.100.87 <pending> 80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:30050/TCP,8060:32063/TCP,853:31120/TCP,15030:30238/TCP,15031:30782/TCP 20h
通常80端口都是映射到31380,使用k8s的節點IP和31380端口就可以訪問我們的測試應用。這個端口號可以通過修改istio-ingressgateway服務來修改。
為了體現我們的流量分配效果,我們再添加一個腳本文件,比如run100.sh:
#!/bin/sh
for i in `seq 100`;
do
curl 127.0.0.1:31380/cat-and-dog
done
執行測試腳本:
$ ./run100.sh
istio-dog-v2
istio-dog-v1
istio-dog-v2
istio-dog-v1
istio-dog-v1
istio-dog-v2
...略
我們並沒有在虛擬服務中指定更詳細的流量策略,所以兩個istio-dog容器是分別提供服務的,基本各占50%流量。這個流量分配並不是虛擬服務提供的,而是基本Service。
我們再來看看另外一種配置方式:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-test-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-test
spec:
hosts:
- "*"
gateways:
- istio-test-gateway
http:
- match:
- uri:
exact: /dog
route:
- destination:
host: istio-dog
- match:
- uri:
exact: /cat
route:
- destination:
host: istio-cat
配置文件保存為istio-test-gateway2.yaml。在執行新的網絡配置前,記着先刪除掉舊的:
$ kubectl delete -f istio-test-gateway1.yaml
gateway.networking.istio.io "istio-test-gateway" deleted
virtualservice.networking.istio.io "istio-test" deleted
$ kubectl apply -f istio-test-gateway2.yaml
gateway.networking.istio.io/istio-test-gateway created
virtualservice.networking.istio.io/istio-test created
此時,訪問根據路徑不同,分配到兩個不同的服務,再到3個Pods:
$ curl http://127.0.0.1:31380/dog
istio-dog-v2
$ curl http://127.0.0.1:31380/dog
istio-dog-v2
$ curl http://127.0.0.1:31380/dog
istio-dog-v1
$ curl http://127.0.0.1:31380/cat
istio-cat-v1
$ curl http://127.0.0.1:31380/cat
istio-cat-v1
再來看一個:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-test-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-test
spec:
hosts:
- "*"
gateways:
- istio-test-gateway
http:
- match:
- uri:
exact: /cat-and-dog
route:
- destination:
host: istio-dog
weight: 20
- destination:
host: istio-cat
weight: 80
保存配置到文件:istio-test-gateway3.yaml。刪除上一版的配置,應用新的配置:
$ kubectl delete -f istio-test-gateway2.yaml
gateway.networking.istio.io "istio-test-gateway" deleted
virtualservice.networking.istio.io "istio-test" deleted
$ kubectl apply -f istio-test-gateway3.yaml
gateway.networking.istio.io/istio-test-gateway created
virtualservice.networking.istio.io/istio-test created
這次是使用權重值,分配到兩個服務的流量比例,到istio-dog的是20%,到istio-cat的是80%。看一看測試的結果:
$ ./run100.sh
istio-cat-v1
istio-cat-v1
istio-dog-v1
istio-dog-v2
istio-cat-v1
istio-cat-v1
istio-cat-v1
istio-cat-v1
istio-cat-v1
istio-cat-v1
istio-dog-v1
istio-cat-v1
istio-cat-v1
istio-cat-v1
istio-dog-v2
...
好吧,我承認我故意誤導了各位。其實apply子命令的真諦本身就在於對於同類型中同名的對象,比如我們上面定義的VirtualService中的istio-test。對於新的配置,在應用時並不需要刪除原來的定義,k8s和Istio都會自動的更新配置。在這個過程中,對外的服務並沒有中斷。
因為有了這樣的機制,才可以實現老版本、新版本並行運行,新版本測試結束后,直接取代老版本的服務完成升級這樣的需求。
我這里每次都強調刪除上一個配置,實際是強調,如果是兩個不同的應用,前一個不使用了,記着一定要回收資源,並且避免端口沖突。
你可以試試直接應用另外一個配置文件來測試一下,感受一下不停機更新的快樂吧。
在容器內,訪問其它容器的服務,同樣是使用基於DNS的服務名稱,比如:istio-cat.default.svc.cluster.local,這個跟k8s是完全一致的。
可以參考官方Bookinfo案例的python源碼。安裝Istio命令行時候的下載包中只有yaml配置文件,python程序的源碼及Dockerfile源碼需要到官方github倉庫中檢索。想要徹底了解運維、研發跟Istio的配合,建議讀一讀源碼。作為示例性的源碼,代碼量很短,注釋也比較全,非常便於理解。倉庫地址:https://github.com/istio/istio/tree/master/samples/bookinfo/src
掌握了上面所說的概念,看過了本文中的示例,現在去看官方關於Bookinfo部分的教程不應當有問題了,建議及早開始,網址在本小節開始的部分給出了。
執行Bookinfo示例,因為網絡原因,建議首先root狀態使用docker提前下載映像文件。因為這種小眾的映像,國內更是難以找到鏡像網站,官方下載速度很慢:
docker pull istio/examples-bookinfo-details-v1:1.8.0
docker pull istio/examples-bookinfo-ratings-v1:1.8.0
docker pull istio/istio/examples-bookinfo-reviews-v1:1.8.0
docker pull istio/examples-bookinfo-reviews-v2:1.8.0
docker pull istio/examples-bookinfo-reviews-v3:1.8.0
docker pull istio/examples-bookinfo-productpage-v1:1.8.0
在Istio安裝的過程中,已經預置了Prometheus和Grafana指標可視化工具,使用Kubectl工具的端口轉發功能把服務端口開放出來就可以使用,比如:
$ kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=prometheus -o jsonpath='{.items[0].metadata.name}') --address 0.0.0.0 9090:9090 &
$ kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') --address 0.0.0.0 3000:3000 &
上面兩條轉發命令中的--address 0.0.0.0
是為了把端口開放給節點主機之外的電腦來訪問,因為通常節點服務器上不具備圖形界面。但這也會帶來安全性的問題,所以注意要使用完即時關閉。
這部分請參考官方文檔:https://istio.io/zh/docs/tasks/telemetry/metrics/querying-metrics/
Istio在輔助調試方面,可以在流量中注入指定的錯誤信息、對數據包進行延遲、中斷等,用於模擬實際故障發生的可能性,從而幫助研發人員、運維人員對系統進行優化。具體使用方法請參考官方文檔。
監控功能和輔助調試同樣也是借助了Sidecar對所有數據流的代理功能得以實現的。
在安全性方面,Istio主要是采用證書簽名機制和TLS連接。支持TLS連接的版本,同本文中我們示例安裝的版本不是同一個。如果有需求,請在Istio集群安裝時就注意選擇對應的配置文件來啟動。詳情請至官方文檔學習。
Istio的使用實驗中,每個配置實驗完成,如果配置不再需要,記着使用kubectl delete -f xxx.yaml的方式來刪除。因為不同於k8s的隨機映射端口出來,Istio通常都使用默認導出的80或者443端口。這樣不同配置之間忘記刪除難免會造成沖突。這時候可以手工刪除,可以參考一下Bookinfo中的 samples/bookinfo/platform/kube/cleanup.sh 腳本。基本做法是使用kubectl get遍歷下面三類對象:
destinationrules virtualservices gateways,其中的內容全部刪除。
結語
本文以用戶需求的演進為導向,面向實際應用串講了服務器端企業級運維的流行技術和流行概念。
重點介紹了Docker/k8s/Istio的安裝、典型使用的入門。
水平所限,錯誤、疏漏在所難免,歡迎批評指正。