前端領域的 Docker 與 Kubernetes

看完本文但願讀者可以瞭解到,Docker 的基本原理,Kubernetes 是怎麼工做的, 對於前端 Kubernetes 有哪些優點與玩法。html

Docker 和傳統部署方式最大的不一樣在於,它將不會限制咱們使用任何工具,任何語言,任何版本的 runtime,Docker 將咱們的應用當作一個只提供網絡服務的盒子(也即容器),Kubernetes 則是對這些盒子進行更多自動化的操做,自動建立,自動重啓,自動擴容,自動調度,這個過程稱之爲容器編排。前端

在今天,容器編排技術給 Web 應用帶來了巨大的靈活性,讓咱們輕鬆建立須要的程序對外提供服務。和傳統的 IaaS 相比,不須要去關心雲主機申請,雲主機配置等信息,也不需考慮雲主機故障致使的服務不可用,由 Kubernetes 的副本控制器幫咱們完成雲主機故障發生後容器遷移。node

本篇文章和你們一塊兒,回顧一下從 Docker 到 Kubernetes 的一些相關內容,最後再看看 Kubernetes 在前端領域有哪些優點和新玩法。linux

Docker 安裝

若是是 Windows10, Windows7 將會使用 VirtualBox 安裝 Linux 做爲 Docker 的宿主機。 Windows10 Pro 會使用 Hyper-V 安裝 Linux 做爲 Docker 的宿主機。github

Docker 基本信息

默認 Docker 存儲位置爲 /var/lib/docker,全部的鏡像,容器,卷都會在這裏,若是你使用了多硬盤,或者掛載了 SSD 不在 / 上,須要修改默認路徑(graph)到合適位置,配置文件爲 /etc/docker/daemon.json, 例如web

{
  "bip": "192.168.0.1/16""graph": "/mnt/ssd/0/docker"
}
複製代碼

Docker 在安裝過程當中會自動建立好 docker0 網卡,並分配 ip 給他。 上面指定的 bip 是指定了 docker0 網卡的 ip , 若是不指定那麼在建立 docker0 時會自動根據主機 ip 選取一個合適的 ip,不過因爲網絡的複雜性,特別是機房網絡內很容易發現地址選取衝突,這時候就須要手動指定 bip 爲一個合適的值。docker 的 ip 選取規則這篇文章分析的很好, 能夠參考 blog.csdn.net/longxing_12…docker

安裝並啓動後能夠經過 docker info 查看Docker的一些配置信息。shell

Docker hello world

Docker 檢查安裝是否正常的第一個測試命令很簡單。

docker run hello-world
複製代碼

首先他會去 Docker Hub 上下載 hello-world 這個鏡像,而後在本地運行這個鏡像,啓動後的這個 Docker 服務稱之爲容器。容器建立後就會執行規定的入口程序,程序執行向流中輸出了一些信息後退出,容器也會隨着這個入口程序的結束而結束。

  • 查看全部容器
docker ps -a
複製代碼

輸出以下:

cf9a6bc212f9        hello-world                     "/hello"                 28 hours ago        Exited (0) 3 min
複製代碼

第一列爲容器 id, 不少針對容器的操做都須要這個 id, 例以下面一些經常使用的操做。

docker rm container_id
docker stop container_id
docker start container_id
docker describe container_id
複製代碼

這裏有個docker start container_id, 啓動一個容器,說明容器即便退出後其資源依然存在,還可使用docker start重啓這個容器。要想讓容器退出後自動刪除能夠在docker run時指定--rm參數。

當咱們運行這個命令時 Docker 會去下載 hello-world 這個鏡像緩存到本地,這樣當下次再運行這條命令時就不須要去源中下載。

  • 查看本地鏡像
docker images
複製代碼

運行 Nginx

Nginx 做爲使用普遍的 Web 服務器在 Docker 世界裏也是一樣流行, 經常用來啓動一個網絡服務驗證網絡配置狀況, 使用下面這條命令啓動 Nginx 容器 docker run --rm -p 80:80 nginx

訪問 localhost:80 端口便可看到 Nginx 服務啓動, 控制檯中能夠看到 Nginx 服務的日誌輸出。

由於 Docker 內的網絡與外部世界是隔離的,因此咱們須要手動指定端口轉發 -p 80:80 來顯式將宿主機的80(前)轉發到容器的80端口, 暴露端口是咱們提供服務最經常使用的使用方式之一。 也有一些其餘類型的服務,例如日誌處理,數據收集須要共享數據卷才能提供服務,全部這些都須要咱們在啓動容器時顯式指定。

Nginx服務

一些常見的啓動參數:

  • -p 本機端口:容器端口 映射本地端口到容器
  • -P 將容器端口映射爲本機隨機端口
  • -v 本地路徑或卷名:容器路徑 將本地路徑或者數據卷掛載到容器的指定位置
  • -it 做爲交互式命令啓動
  • -d 將容器放在後臺運行
  • --rm 容器退出後清除資源

Docker是如何工做的

Docker 的底層核心原理是利用了 Linux 內核的 namespace 以及 cgroup 特性,其中 namespace 進行資源隔離,cgroup 進行資源配額, 其中 Linux 內核中一共有 6 種 namespace,分別對應以下。

Namespace 系統調用函數 隔離內容
UTS CLONE_NEWUTS 主機與域名
IPC CLONE_NEWIPC 信號量、消息隊列和共享內存
PID CLONE_NEWPID 進程編號
Network CLONE_NEWNET 網絡設備、網絡棧、端口等
Mount CLONE_NEWNS 掛載點(文件系統)
User CLONE_NEWUSER 用戶和用戶組

在系統調用中有三個與namespace有關的函數:

  1. clone man7.org/linux/man-p…

若是我想讓子進程擁有獨立的網絡地址,TCP/IP 協議棧,能夠下面這樣指定。

clone(cb, *stack , CLONE_NEWNET, 0)
複製代碼
  1. unshare man7.org/linux/man-p…

將當前進程轉移到新的 namespace 中, 例如使用 fork 或 vfork 建立的進程將默認共享父級資源,使用 unshare 將子進程從父級取消共享。

  1. setns man7.org/linux/man-p…

給指定的PID指定 namespace, 一般用於共享 namespace。

Linux 在內核層支持了在系統調用中隔離 namespace, 經過給一個進程分配單獨的 namespace 從而讓其在各個資源維度進行隔離,每一個進程都能獲取到本身的主機名,IPC, PID, IP, 根文件系統,用戶組等信息,就像在一個獨佔系統中,不過雖然資源進行了隔離,可是內核仍是共享同一個,這也是比傳統虛擬機輕量的緣由之一。

另外只有資源進行隔離還不夠,要想保證真正的故障隔離,互不影響, 還須要對針對 CPU, 內存,GPU 等進行限制,由於若是一個程序出現死循環或者內存泄露也會致使別的程序沒法運行。 資源配額是使用內核的 cgroup 特性來完成,想了解細節的同窗能夠參考: www.cnblogs.com/sammyliu/p/… 。 (另外強烈推薦在 Linux 4.9 以上的內核跑容器,Linux 3.x 中有已知內核不穩定致使主機重啓的問題)

Docker 網絡

一個容器要想提供服務,就須要將自身的網絡暴露出去。Docker 是與宿主機上的環境是隔離的,要想暴露服務就須要顯示告訴 Docker 哪些端口容許外部訪問,在運行 docker run -p 80:80 nginx 時這裏就是將容器內部的 80 端口暴露到宿主機的 80 端口上,具體的端口轉發下面會具體分析一下。容器的網絡部分是容器中最重要的部分,也是構建大型集羣的基石,在咱們部署 Docker 的應用時,須要要對網絡有個基本的瞭解。

Docker 提供了四種網絡模式,分別爲 HostContainerNoneBridge 使用 --net 進行指定

Host 模式:

docker run --net host nginx
複製代碼

Host 模式不會單獨爲容器建立 network namespace, 容器內部直接使用宿主機網卡,此時容器內獲取 ip 爲宿主機 ip,端口綁定直接綁在宿主機網卡上,優勢是網絡傳輸時不用通過 NAT 轉換,效率更高速度更快。

Container 模式:

docker run --net container:xxx_containerid nginx
複製代碼

和指定的 container 共享 network namespace, 共享網絡配置,ip 地址和端口,其中沒法共享網絡模式爲 Host 的容器。

None 模式:

docker run --net none busybox ifconfig
複製代碼

指定爲 None 模式的容器內將不會分配網卡設備,僅有內部 lo 網絡。

None 網絡

Bridge 模式

docekr run --net bridge busybox ifconfig
複製代碼

Bridge網絡

該模式爲默認模式,容器啓動時會被分配一個單獨的 network namespace,同時 Docker 在安裝/初始化時會在宿主機上建立一個名爲 docker0 的網橋,該網橋也做爲容器的默認網關,容器網絡會在該網關網段內進行 ip 的分配。

當我執行 docker run -p 3000:80 nginx 時,Docker 會在宿主機上建立下面一條 iptable 轉發規則。

iptables

最底下的規則顯示當外部請求主機網卡 3000 端口時將它進行目的地址轉換(DNAT), 目的地址修改成 172.18.0.2,端口修改成 80,修改好目的地址後流量會從本機默認網卡通過 docker0 轉發到對應的容器,這樣當外部請求宿主機的 3000 端口,內部會將流量轉發給內部容器服務,從而實現服務的暴露。

DNAT

一樣 Docker 內部訪問外部接口也會進行源地址轉換(SNAT), 容器內部請求 google.com, 服務器上收到的將是主機網卡的 ip。

SNAT

Bridge 模式因爲多了一層 NAT 轉換因此效率會比 Host 模式差一些,可是可以很好的隔離外部網絡環境,讓容器獨享 ip 且具備完整的端口空間。

上面四種網絡模式是 Docker 自帶的幾種工做方式,可是部署 Kubernetes 須要全部的容器都工做在一個局域網中,因此在部署集羣時須要多主機網絡插件的支持。

Flannel

多主機網絡解決方案有 CNCF 推出的 CNI 規範以及 Docker 自帶的 CNM 方案,可是目前你們用的最多的仍是 CNI 規範,其中一種實現就是 Flannel。

Flannel 使用了報文嵌套技術來解決多主機網絡互通問題,將原始報文進行封包,指定包ip爲目的主機地址,等包到達主機後再進行拆包傳送到對應的容器。下圖顯示 flannel 使用效率更高的 UDP 協議來在主機間傳輸報文。

flannel

目前主流跨主機通訊目前經常使用的有三種,各有優缺,視場景選擇:

  • overlay, 即上面的報文嵌套。
  • hostgw 經過修改主機路由表實現轉發,不須要拆包和封包,效率更高,但一樣限制比較多,只適合在相同局域網中的主機使用。
  • 使用軟件實現的 BGP(邊界網關協議)以此向網絡中的路由器廣播路由規則。和 hostgw 同樣不須要拆包,可是實現成本較高。

有了CNI才能在此基礎上構建 Kubernetes 集羣。

Kubernetes 介紹

在小規模場景下使用 Docker 能夠一鍵部署應用確實很方便,達到了一鍵部署的目的,可是當出現須要在幾百臺主機上進行多副本部署,須要管理這麼多主機的運行狀態以及服務的故障時須要在其餘主機重啓服務,想象一下就知道手動的方式不是一種可取的方案,這時候就須要利用 Kubernetes 這種更高維度的編排工具來管理了。Kubernetes 簡稱 K8S, 簡單說 K8S 就是抽象了硬件資源,將 N 臺物理機或雲主機抽象成一個資源池,容器的調度交給 K8S 就像親媽同樣照顧咱們的容器,CPU 不夠用就調度到一臺足夠使用的機器上,內存不知足要求就會尋找一臺有足夠內存的機器在上面建立對應的容器,服務由於某些緣由掛了, K8S 還會幫咱們自動遷移重啓, 簡直無微不至,至尊享受。咱們做爲開發者只關心本身的代碼,應用的健康由 K8S 保證。

這裏就不介紹具體的安裝方式了,若是使用 Windows 或者 MacOS 能夠直接使用 Docker Desktop 下的 Kubernetes 選項一鍵安裝單主機集羣,也可使用 kind 工具 在本地模擬多集羣 K8S。

K8S 調度的基本單位爲 pod, 一個 pod 表示一個或多個容器。引用一本書裏所說

之因此沒有使用容器做爲調度單位,是由於單一的容器沒有構成服務的概念;例如 Web 應用作了先後端分例,須要一個 NodeJS 與 Tomcat 才能組成一個完整的服務,這樣就須要部署兩個容器來實現一個完整的服務,雖然也能夠把他們都放到一個容器裏,但這顯然違反了一個容器即一個進程的核心思想 --《Service Mesh實戰 - 用 istio軟負載實現服務網格》

K8S 與傳統 IaaS 系統的不一樣:

IaaS 就是 Infrastructure as a service, 所謂基礎設施即服務,開發者想要上線一個新應用須要申請主機,ip, 域名等一系列資源,而後登陸主機自行搭建所需環境,部署應用上線,這樣不只不利於大規模操做,並且還增長了出錯的可能,運維或開發這經常本身寫腳本自動化完成,遇到一些差別再手動修改腳本,很是痛苦。

K8S 則是將基礎設施可編程化,由原來的人工申請改成一個清單文件自動建立,開發者只須要提交一份文件,K8S 將會自動爲你分配建立所需的資源。對這些設施的 CRUD 均可以經過程序的方式自動化操做。

爲了瞭解 K8S 的基礎概念,下面來部署一個 Node SSR 應用:

初始化應用模板

npm install create-next-app
npx create-next-app next-app
cd next-app
複製代碼

建立好工程後給添加一個 Dockerfile 用來構建服務的鏡像

Dockerfile

FROM node:8.16.1-slim as build

COPY ./ /app 
WORKDIR /app RUN npm install RUN npm run build RUN rm -rf .git 

FROM node:8.16.1-slim

COPY --from=build /app / 
EXPOSE 3000
WORKDIR /app 
CMD ["npm""start"] 複製代碼

這個 Dockerfile 作了兩部分優化

  1. 使用精簡版的 node 基礎鏡像, 大大減小鏡像體積
  2. 使用分步構建的方式, 可以減小鏡像層數以及移除臨時文件從而減小了鏡像體積。

構建鏡像

docker build  . --tag next-app
複製代碼

以後咱們就能夠向 Kubernetes 提出咱們應用的要求了。爲了保證高可用,服務至少建立兩個副本,咱們還須要一個應用的域名當這個域名請求到咱們集羣上時自動轉發到咱們的服務上。那麼咱們對應的配置文件就能夠這麼寫

Deployment.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
 name: app-ingress
spec:
 rules:
 - host: next-app-server
 http:
 paths:
 - backend:
 serviceName: app-service
 servicePort: 80

---
kind: Service
apiVersion: v1
metadata:
 name: app-service
spec:
 selector:
 app: web
 ports:
 - port: 80
 targetPort: 3000

---

apiVersion: apps/v1
kind: Deployment
metadata:
 name: app-deployment
spec:
 replicas: 2
 selector:
 matchLabels:
 app: web
 template:
 metadata:
 labels:
 app: web
 spec:
 containers:
 - image: next-app
 name: next-app
 imagePullPolicy: IfNotPresent
 ports:
 - containerPort: 3000
複製代碼

上面這個清單告訴 K8S:

  • 首先須要一個 Deployment 控制器,鏡像爲 next-app, 服務端口爲 3000,給我建立兩個副本。
  • 還須要建立一個 Service, 這個 Service 指向由副本控制器建立的幾個 next-app
  • 申請一個 Ingress 入口, 域名爲 next-app-server, 其指向剛剛的 Service。

提交這份申請給 K8S。

kubectl apply -f ./Deployment.yaml
複製代碼

接着就能夠看到已經部署的 pod。

sh-4.4$ kubectl get pod
NAME                              READY     STATUS    RESTARTS   AGE
app-deployment-594c48dbdb-4f4cg   1/1       Running   0          1m
app-deployment-594c48dbdb-snj54   1/1       Running   0          1m
複製代碼

而後瀏覽器打開 Ingress 裏配置的域名便可訪問對應的應用(前提是這個域名可以打到你的 K8S 集羣節點上)。

上面的清單主要建立了三種最多見資源來保證服務的運行, 這也是 Kubernetes 的最主要的三類資源。

  • Ingress

    L7層負載均衡配置, 能夠根據不一樣的域名或者路徑等信息指向不一樣的 Service, Ingress 和 Nginx 很像,實際上 Ingress 的一種實現就是 Nginx, 因此能夠將 Ingress 來當成 Nginx 來用,只不過咱們不須要手動修改 nginx.conf,也不用手動重啓 Nginx 服務。

  • Service

    一組 pod 的抽象,用來選擇提供同一服務的 pod。 因爲 pod 是不穩定的,銷燬重建常常發生,pod 的 ip 常常發生變化,因此須要一種抽象的資源 Service 來表示 pod 的位置。 Service 也是K8S內部服務發現機制,會自動將 Service 名稱寫入內部 DNS 記錄中。

  • Deployment

    副本控制器,用來管理維護 pod 的一種機制。經過 Deployment 能夠指定副本數量,發佈策略, 記錄發佈日誌並支持回滾。

應用發佈系統

K8S 僅僅負責容器的編排,實際上若是部署應用還須要外部 Pipeline 的支持,代碼的構建,靜態檢查,鏡像的打包由 Pipeline 完成.

;

目前國內用的比較多的發佈系統經常由下面幾個服務組成: GitLab/GitHub, Jenkins, Sonar, Harbor。

K8S 在前端的優點

  1. 首先前端應用和 Java 不一樣,一個小型 NodeJS 服務佔用內存僅 40M 左右,這意味着若是咱們有不少 NodeJS 應用,使用 K8S 將節省大量的硬件資源。

  1. 使用容器的思想進行非侵入式日誌,性能指標收集。

因爲容器便是一個進程,因此對容器的監控能夠看做對咱們 NodeJS 進程的監控,K8S 生態裏已經有不少成熟的容器監控方案,例如 Prometheus + Grafana, 使用此能夠達到應用的非侵入式性能指標的收集包括: 網絡IO / 磁盤IO / CPU / MEM。

一樣對於日誌收集,咱們在代碼中能夠直接使用console的方式輸出, 在容器維度再使用日誌收集服務進行日誌收集,一樣的非侵入式, 代碼層無感知,對開發者更加友好,將日誌和服務解耦。

  1. 前端微服務架構基礎設施層。

微服務架構是近兩年愈來愈流行的一種前端架構組織方式,微服務架構須要有一種更加彈性靈活的部署方式。 使用 Docker 讓咱們在複雜架構中抽象服務的最小單元,K8S 給自動維護大規模集羣提供了可能。能夠說微服務架構自然適合使用 K8S。

K8S 新玩法, 流量分配

K8S 中使用 Service 來抽象一組 pod,而 Service 的選擇器能夠動態變動,因此了咱們不少可能的玩法, 好比藍綠髮布系統。

藍綠髮布是指發佈過程當中新應用發佈測試經過後,經過切換網關流量, 一鍵升級應用的發佈方式, 在 K8S 中經過動態更新 Service 的選擇器實現不一樣版本的一鍵切換

下面使用上面的 Next.js 應用來演示一下藍綠髮布,倉庫地址

git clone https://github.com/Qquanwei/test-ab-deploy
cd test-ab-deploy
docker build . --tag next-app:stable
kubectl apply -f ./Deployment.yaml
複製代碼

這裏會將 next-app:stable 這個鏡像部署到集羣中,而且給 pod 打上 version: stable 的tag。

部署後打開顯示以下。

接着,咱們部署 test 分支, 這個分支咱們會構建爲 next-app:test 的鏡像,而且部署時給這個pod打上 version: test 的標籤。

git checkout test
docker build . --tag next-app:test
kubectl apply -f ./Deployment.yaml
複製代碼

這時候咱們一共部署了兩個版本的應用,並且都已經就緒狀態。

可是因爲咱們的 Service 爲 version=stable, 因此全部的請求並不會打到 test 版本上,仍然都會請求 stable 的服務。

當咱們用其餘的方式已經驗證 test 版本服務可用時, 例如配另一個 Service 用來測試(Good), 這時候能夠下面一條指令切換當前的 Service 到 test 應用上。

kubectl apply -f ./switch-to-test.yaml
複製代碼

執行完這條命令後,刷新頁面能夠看到以下。

經過切換 Service 的方式很輕鬆就實現了藍綠髮布的功能,並且是瞬間完成,由於 Service 是 K8S 裏比較輕量的資源,不會和隔壁 Nginx 同樣修改配置就要重啓服務影響整個線上服務。固然實際生產環境會比演示更加嚴謹,可能有專門的平臺以及審覈人員進行每一個操做的二次驗證。

對於藍綠, 灰度發佈方式,使用 K8S 能夠較爲輕鬆地實現,讓咱們可以有更多的方式去驗證想法。不過若是想實現更加高級的流量分配方案(例如A/B發佈),須要複雜的流量管理策略 (鑑權,認證),就須要用到服務網格了。

Istio 是目前比較流行的服務網格框架,相比於 K8S 注重運行容器的管理, Istio 則是更注重容器之間組成的服務網格的流量傳輸。

下圖是 Istio 捕獲的官方示例的 bookinfo 微服務中服務的拓撲結構和一些數據指標。

使用 Istio 有兩個明顯的好處:

  1. Istio 可以捕捉到服務間的調用鏈路,並且不入侵用戶代碼。
  2. Istio 可以對每一條鏈接,進行單獨的管理。

例如,咱們能夠輕鬆對的不一樣版本的 review 應用的 v1, v2, v3 版本進行動態權重分配。

不只僅能夠對流量權重分配,並且還能夠制定一些A/B方案,例如根據 URL 是否匹配請求不一樣的版本應用,或者根據 Header 種下的 Cookie 進行用戶的區分,從而請求不一樣的應用。固然,面對行業場景不一樣,Istio 還會誕生不少有趣的玩法。

不過缺點一樣存在,Istio 實際上也是一個很複雜的系統,會對性能形成影響,並且會佔用不小的系統資源。

總結

K8S 是劃時代的,隨着將來的發展微服務化,雲原生化將會是咱們的應用的主要形式,對於前端而言 K8S 無疑會改變現有前端的開發方式和前端架構,讓前端可以更迅速地擴展,更穩定地交付,應用之間的聯繫也會越發緊密。沉寂已久的前端下一個三年相信將會是微服務架構的天下,K8S 做爲微服務架構基礎設施層也將會被愈來愈多的公司團隊所重視。

參考資料

本文發佈自 網易雲音樂前端團隊,文章未經受權禁止任何形式的轉載。咱們一直在招人,若是你剛好準備換工做,又剛好喜歡雲音樂,那就 加入咱們

相關文章
相關標籤/搜索