輕鬆構建微服務之docker和高效發佈

微信公衆號:內核小王子
關注可瞭解更多關於數據庫,JVM內核相關的知識;
若是你有任何疑問也能夠加我pigpdong[^1]java

docker

咱們先來了解下docker的原理,如何才能製造出一個真正隔離的軟件運行環境.node

namespace

docker在建立容器進程的時候能夠指定一組namespace參數,這樣容器就只能看到當前namespace所限定的資源,文件,設備,網絡。用戶,配置信息,而對於宿主機和其餘不相關的程序就看不到了,PID namespace讓進程只看到當前namespace內的進程,Mount namespace讓進程只看到當前namespace內的掛載點信息,Network namespace讓進程只看到當前namespace內的網卡和配置信息,python

docker利用namespace機制隔離出一個軟件執行環境,咱們能夠用下圖來將docker和虛擬機技術作一個對比mysql

image

一個centOS的KVM啓動起來後,即便什麼不作也須要消耗200M的內存,並且用戶進程運行在虛擬機裏,對宿主機操做系統的調用不可避免會受到虛擬化軟件的攔截,而容器化的應用依然是宿主機上的一個普通進程.linux

cgroup

全名 linux control group,用來限制一個進程組可以使用的資源上限,如CPU,內存,網絡等,另外Cgroup還可以對進程設置優先級和將進程掛起和恢復,cgroup對用戶暴露的接口是一個文件系統,/sys/fs/cgroup下 這個目錄下面有 cpuset,memery等文件,每個能夠被管理的資源都會有一個文件,如何對一個進程設置資源訪問上限呢?在/sys/fs/cgroup目錄下新建一個文件夾,系統會默認建立上面一系列文件,而後docker容器啓動後,將進程ID寫入taskid文件中,在根據docker啓動時候傳人的參數修改對應的資源文件nginx

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

Linux Cgroup的設計仍是比較易用的,簡單理解就是一個子系統目錄加上一組資源限制文件,而對docker等linux容器項目而言,只須要在每一個子系統下面,爲每一個容器建立一個控制組也就是一個文件夾,讓後在容器啓動後寫入進程PID.git

文件系統

即便開啓了Mount Namespace,容器進程看到的文件系統仍是和宿主機同樣的,mount namespace修改的時容器進程對掛載點的認知,他對容器進程視圖的改變,必定伴隨掛載操做才生效,可是做爲一個普通用戶,咱們但願在容器內看到的文件系統就是一個獨立的隔離環境,而不是繼承自宿主機上的文件系統.算法

咱們能夠在容器啓動後立刻掛載他的整個根目錄,這個掛載對宿主機不可見,而後經過chroot來更改change root file system更改進程的根目錄到掛載的位置,通常會經過chroot掛載一個完整的linux的文件系統,可是不包括linux內核,這樣當咱們交付一個docker鏡像的時候不只包含須要運行的程序還包括這個程序依賴運行的這個環境,由於咱們打包了整個依賴的linux文件系統,對一個應用來講,操做系統纔是他所依賴的最完整的依賴庫,sql

這個掛載在容器根目錄下,用來爲容器進程提供隔離後的執行環境的文件系統,就是所謂的容器鏡像,它還有一個專業的名詞rootFSdocker

因爲rootfs裏面打包的不只僅是用戶程序,而是整個系統的文件目錄,也就是是應用和應用依賴的類庫和環境變量都在裏面.

增量層

docker在鏡像的設計中引入層的概念,也就是用戶在製做docker鏡像中的每一次修改都是在原來的rootfs上新增一層roofs,以後經過一種聯合文件系統union fs的技術進行合併,合併的過程當中若是兩個rootfs中有相同的文件則會用最外層的文件覆蓋原來的文件來進行去重操做,舉個例子,咱們從鏡像中心pull一個mysql的鏡像到本地,當咱們經過這個鏡像建立一個容器的時候,就在這個鏡像原有的層上新加了一個增roofs,這個文件系統只保留增量修改,包括文件的新增刪除,修改,這個增量層會藉助union fs和原有層一塊兒掛載到同一個目錄,這個增長的層能夠讀寫,原有的其餘層只能讀,這樣保證了全部對docker鏡像的操做都是增量,以後用戶能夠commit這個鏡像將對這個鏡像的修改生成一個新的鏡像,新的鏡像就包含了原有的層和新增的層,只有最原始的層纔是一個完整的linux fs, 那麼既然只讀層不容許修改,那麼我怎麼刪除只讀層的文件呢,這個時候只須要在讀寫層也就是最外層生成一個whiteout文件來遮擋原來的文件就能夠了,

image

$ docker image inspect ubuntu:latest
...
     "RootFS": {
      "Type": "layers",
      "Layers": [
        "sha256:f49017d4d5ce9c0f544c...",
        "sha256:8f2b771487e9d6354080...",
        "sha256:ccd4d61916aaa2159429...",
        "sha256:c01d74f99de40e097c73...",
        "sha256:268a067217b5fe78e000..."
      ]
    }

dockerfile

能夠經過docfile生成一個鏡像,docfile裏面能夠指定 from的原始鏡像,以及自定義操做例如拷貝文件等,容器啓動命令等

例以下面的dockerfile,FROM原語指定了原始鏡像是python:2.7-slim,避免了先從一個原始的ubantu鏡像,而後經過apt-get install按照python

WORDIR 切換工做目錄

ADD拷貝文件

RUN 在容器裏執行shell

CMD 指定容器進程,至關於 docker rum python app.py

# 使用官方提供的 Python 開發鏡像做爲基礎鏡像
FROM python:2.7-slim

# 將工做目錄切換爲 /app
WORKDIR /app

# 將當前目錄下的全部內容複製到 /app 下
ADD . /app

# 使用 pip 命令安裝這個應用所須要的依賴
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# 容許外界訪問容器的 80 端口
EXPOSE 80

# 設置環境變量
ENV NAME World

# 設置容器進程爲:python app.py,即:這個 Python 應用的啓動命令
CMD ["python", "app.py"]

容器網絡

從以前的章節中咱們能夠看到,容器只是一個被隔離的進程,這個進程只能看到他本身network namespace下的網絡棧,所謂的網絡棧包括了
網卡(network interface),迴環設備(loopback device),路由表(route table),iptables規則,這些已經能夠構成這個 進程和外界進行網絡通信的基本環境.

容器做爲宿主機內的一個進程,在不設置network namespace的狀況下,可使用宿主機的網絡棧.

$ docker run –d –net=host --name nginx-host nginx

以上例子,就是容器採用宿主機的網絡,當容器啓動後監聽的是宿主機上的80端口

採用宿主機網絡能夠爲容器提供最好的網絡性能,可是咱們但願容器有一個徹底被隔離的環境,包括網絡環境,她應該有本身的IP地址和端口,這樣就不會和其餘進程有端口衝突,例如我運行4個ngnx容器進程,我但願他們是徹底同樣的,向運行在4臺宿主機裏面同樣,都監聽80端口.因此咱們但願容器進程運行在本身的network namespace內.

咱們如今把容器當作,一臺主機,兩臺主機之間想要通訊,就須要拉一條網線,多個主機之間想要通訊,就須要一個交換機用網線把他們連起來,在linux上,可以啓到虛擬交換做用的設備就是網橋(Bridge),他是一個工做在數據鏈路層的設備,咱們能夠把它當成一個二層交換機,而二層設備主要靠學習MAC地址對應的端口,並將數據包轉發到對應的端口上去.

docker在安裝的時候會在宿主機上建立一個叫docker0的網橋,而容器可用經過Veth Pair的虛擬設備,鏈接到這個網橋,Veth Pair這種設備在建立的時候會有兩張虛擬網卡,從一個網卡里面發出的數據包會到達另一個網卡里,而且這兩個網卡能夠跨network namespace,這樣Veth Pair能夠理解爲鏈接不一樣namespace的網線.

下圖是我啓動了兩個nginx容器,分別爲nginx-1 ,nginx-2,咱們用ifconfig查看宿主機上的網卡信息

$ docker run –d --name nginx-1 nginx
$ docker run –d --name nginx-2 nginx

image

被插在網橋上的虛擬網卡,不會調用網絡協議中棧處理數據包,只會像一個端口同樣,將數據包交給網橋,由網橋進行轉發.

下面咱們分析下一個宿主機內的兩個容器之間的網絡怎麼通信?

當咱們在容器nginx-1內去ping一下nginx-2的ip,默認下是通的

image

  • 1.宿主機內的兩個主機經過二層網絡相通,nginx-1會先發一個ARP包來獲取nginx-2的MAC地址
  • 2.nginx-1只能看到他本身network namespace內的網卡 nginx-1-eth-0 ,因此數據包從這個網卡發出,可是這個網卡是一個Veth Pair設備,這個設備的另一端在宿主機上默認的namespace內,而且是插在網橋docker-0上
  • 3.因爲Veth Pair設備的做用,宿主機的虛擬網卡收到數據包後,直接交給網橋docker-0進行轉發,而docker-0會把數據包廣播給插在這個網橋上的其餘虛擬網卡
  • 4.這樣數據就會被廣播到宿主機上的另一個虛擬網卡上了,這個虛擬網卡也是一個Veth Pair,他的另一端是nginx-2容器的namespace內的虛擬網卡,這個網卡將本身MAC地址回覆
  • 5.這樣nginx-1的namespace內的虛擬網卡就獲取到了nginx-2的namespace內的網卡的MAC地址了,就能夠組裝數據包將請求發給nginx-2了
  • 6.一樣數據包先根據Veth Pair設備到達宿主機namespace內的網卡,而後交給docker-0進行轉發,因爲此時docker-0網橋已經學習到了nginx-2的mac地址對應的端口了,只須要查CAM表查一下記錄,轉發到另一塊宿主機的虛擬網卡,而後到達nginx-2的namespace內的網卡

以上就是同一個宿主機內的不一樣docker容器經過Veth Pair設備和docker-0網橋通訊的流程,與此相似,容器和其餘宿主機進行通訊,docker-0網橋在轉發的時候會根據宿主機的路由規則,將數據轉發給宿主機上的eth-0網卡,而後在由宿主機上德etho-進行轉發.

那麼容器怎麼和其餘宿主機內的網絡通訊呢?

在docker默認配置下,一臺宿主機內的docker-0網橋和另一個宿主機內的docker-0網橋沒有任何關聯,它們之間沒辦法相互關聯,因此連在不一樣網橋上的容器沒有辦法進行連通.咱們能夠經過軟件的方式,建立一整個集羣共用的網橋,集羣內全部的容器都連到這個網橋上,這個就是覆蓋網絡(overlay),爲了實現這個網橋,咱們須要瞭解FLANNEL技術,以及他的量種實現:UDP,VXLAN

UDP的模式是在宿主機內增長一個軟件進程,經過TUN設備,將數據包發給上層應用,而後在由應用層判斷如何進行轉發,目前UDP模式已經被淘汰,主要由於性能過低,涉及太屢次數據包從內核態到用戶態的轉發.

image

VXLAN是linux內核自己就支持的一種網絡虛擬化技術,VXLAN維護一個虛擬的局域網,使在這個LAN之內的容器能夠相互通訊.

image

從上圖能夠看出,爲了能讓二層網絡可以打通,VXLAN須要在宿主機上設置一個特殊的設備做爲隧道的兩端,這個設備就是VTEP (Vxlan Tunnel Endpoin ) 虛擬隧道端點.而VTEP的做用和上面UDP的應用程序相似,他會拆包和封包,只不過他在數據鏈路層而不是涉及用戶太和內核態的轉換.

kubernetes

k8s是一個作容器編排和調度的工具,kubernets的最小調度單元是POD,一個POD能夠管理一組同生命週期的容器,k8s提供一個restful的客戶端api供用戶使用,因此會有一個APIserver來接受請求,經過etcd做爲數據庫來存儲請求中得CRUD操做,而其餘模塊例如控制器中的調度單元,會掃描數據庫中的記錄,若是有新的POD尚未分配物理節點,則會執行調度動做,若是發現新增了副本數量,就會增長POD副本,若是修改了POD相關配置就去執行,而每個節點上面都會容許一個kube-proxy用來接收外部請求後轉發,而docker採用的插拔容器,可使用docker引擎,也能夠用其餘的引擎。

image

下面咱們分析下k8s下相關的概念

  • 1.POD POD是kubernate的最小可操做單元,若是咱們把容器當作一個特殊的進程,那POD就是一個有相同生命週期的進程組,POD裏的全部容器共享一個Network namespace,而且能夠共享一個Volume,那麼kubernate怎麼實現呢? k8s會在容器建立的時候先建立一箇中間容器infra,而後其餘的容器和這個容器共享namespace來實現.這樣一個POD內的多個容器能夠經過localhost進行通訊,一個POD只有一個IP地址,POD的生命週期和infra相關,和容器A和B無關.

  • 2.MASTER Kubernatis集羣中的master節點,能夠部署在物理機或者虛擬機上,master節點負責維護集羣的狀態,以及對外提供API服務,master節點上部署如下3種組件

  • 2.1 API SERVER,做爲kubernatis系統的入口,封裝了核心對象的增刪改查操做以及配置操做,以RESTFUL api的方式以及命令行的方式kubectl將接口暴露給外部客戶和內部組件使用,維護的對象會被持久化到etcd中.

  • 2.2 Schedule 調度器:爲新建的POD選擇NODE,也就是分配機器,這個調度器也是能夠插拔的,能夠換成其餘調度器,調度器能夠考慮根據NODE的負載以及物理位置等參數設計調度算法.

  • 2.3 Controller 控制器:全部資源的自動化控制中心,是一個大總管,例如Node Controller 用於管理NODE對象,接收NODE節點的使用狀況,和NODE生命週期的管理即:建立和銷燬,以及心跳檢查等,Replication Controller ,副本控制器,監測業務集羣中POD數量是否符合預期,若是不夠會建立,多餘會銷燬,當管理員修改了預期的值後,該進程就會進行響應.

  • 3.NODE 節點是kubernatis集羣中,相對於master而言的工做主機,這些主機能夠說物理機,也能夠是虛擬機,POD容器都運行在這些工做節點上,每一個Node上都運行着一個叫kublet的進程,用來啓動和管理POD,NODE被master管理,一個NODE宕機後,master會將這臺node上部署的POD在其餘NODE上從新建立並部署起來,NODE節點上部署有如下組件.

  • 3.1 kubelet,做爲一個單獨進程運行在NODE節點上,主要功能是:容器的管理,鏡像的管理,數據卷的管理,同時kubelet也提供restful接口服務,當master節點的控制器能夠下發請求到node上的kubelet進程,進行某個POD的建立,啓動容器和銷燬,並監聽容器運行狀態彙報給master.

  • 3.2 kube-proxy,負責爲POD建立代理對象,用來實現訪問POD提供服務的網絡請求的路由和轉發,該proxy能夠提供必定的負載均衡和SDN的功能

  • 3.3 容器環境,能夠是docker也能夠是其餘容器技術

  • 4.etcd做爲一個鍵值數據庫,存儲集羣的元數據,以及POD的配置信息,並具備消息訂閱功能,因此,當用戶修改了POD預期副本數量,Replication Controller就能夠感知到, etcs採用raft協議來保證集羣環境下的數據一致性,並提供restful的api來供客戶端使用,kubernatis中的網絡配置信息,以及操做記錄和各個對象的狀態都存儲在etcd中,多個組件間的交互都須要用到etcd,因此etcs對整個k8s集羣特別重要,因此etcd必須保證高可用.

image

Devops

Devops用來保證,開發,運維,測試之間的高效溝通和協做,是軟件發佈更加簡單和便捷.咱們能夠簡單的將Devops思想抽象成兩個產品,一個產品用來作項目管理,一個產品用來作軟件發佈和集成.

咱們能夠嘗試簡單思考下一個項目管理的系統須要提供哪些功能:

  • 1.支持看板視圖,讓團隊能夠快速作信息同步,面板信息應該包括:項目描述,項目進度以及碰到的問題,而項目進度能夠根據時間列出:需求立項,需求評審,設計評審,技術方案評審,開發,測試,發佈.

  • 2.項目迭代管理,提供各類維度報表,用來分析和預估後期項目週期.

  • 3.代碼關聯,能夠將需求在gitlab上關聯代碼

而一個軟件發佈的系統應該提供如下功能

  • 1.代碼管理,代碼提交能夠關聯對應的需求,支持代碼在線review

  • 2.持續集成,支持每日定時,或者代碼提交自動觸發構建動做

  • 3.支持從gitlab上拉取最新的代碼,而後在特定環境下打包,而後根據dockerfile生成鏡像並提交

  • 4.支持鏡像管理

  • 5.支持自動化單元測試,接口測試,性能測試

  • 6.持續部署,支持根據生成的鏡像自動部署測試環境和開發環境,使開發環境能夠隨時訪問

  • 7.灰度發佈,能夠灰度發佈到生產環境和預發佈環境

咱們如何利用k8s作到自動化打包和發佈?

  • 1.建立pileline 指定項目名稱和對應的tag,以及依賴工程,一個pipeline指一個完整的項目生命週期(開發提交代碼到代碼倉庫,打包,部署到開發環境,自動化測試,部署到測試環境,部署到生產環境)
  • 2.根據項目名稱和tag去gitlab上拉取最新的代碼(利用java裏的Runtime執行shell腳本)
  • 3.利用maven進行打包,這個時候能夠爲maven建立一個單獨的workspace(shell腳本)
  • 4.根據預先寫好的docfile,拷貝maven打的包生成鏡像,並上傳鏡像 (shell腳本)
  • 5.經過k8s的api在測試環境發佈升級
  • 6.經過灰度等方案發布到生產環境

藍綠髮布

通常在反向代理層,例如nginx,的配置文件作一個軟鏈接,分別指向兩套環境,咱們將這兩套環境分別稱爲生產環境和預發佈環境,正常狀況下配置文件的軟鏈接指向生成環境,這個時候流量都會進入到生成環境的集羣,發佈的時候先發布預發佈環境的機器,因爲沒有流量進入,因此機器重啓沒有太大影響,機器發佈完成後,能夠經過其餘入口,例如內部修改host或者其餘流量入口進行內部測試,若是內部測試經過,就能夠修改軟鏈接指向預發佈環境的集羣,而後在進行驗證當外部流量進來後,是否正常,若是不正常能夠直接在把流量切回去,若是正常就能夠升級生成環境的機器,而後在把流量切到生產環境

灰度發佈

灰度發佈能夠在發佈前,將一部分比例的機器的流量切走,而後進行軟件升級,升級完成後把流量切回來,而後進行驗證,驗證有問題在把流量掐掉,沒有問題能夠慢慢按比例對外,這種方式須要新老版本的服務同時對外提供服務,有些狀況下咱們把經過白名單或者固定用戶的形式開發服務也叫灰度.

相關文章
相關標籤/搜索