Docker&K8s學(xué)習(xí)
Docker主要包括三部分
**Image **鏡像
一種特殊的文件系統(tǒng),它除了提供容器運(yùn)行時(shí)所需的程序、庫(kù)、資源、配置等文件外,還包含了一些為運(yùn)行時(shí)準(zhǔn)備的一些配置參數(shù)(例如環(huán)境變量)。鏡像不包含任何動(dòng)態(tài)數(shù)據(jù),其內(nèi)容在構(gòu)建之后也不會(huì)被改變。
Dockerfile制作說(shuō)明
https://docs.docker.com/engine/reference/builder/
Container 容器
運(yùn)行鏡像服務(wù)
Repository 倉(cāng)庫(kù)
托管公共鏡像
Docker和虛擬機(jī)的區(qū)別

K8S介紹
Kubernetes,就是基于容器的集群管理平臺(tái)
就在Docker容器技術(shù)被炒得熱火朝天之時(shí),大家發(fā)現(xiàn),如果想要將Docker應(yīng)用于具體的業(yè)務(wù)實(shí)現(xiàn),是存在困難的——編排、管理和調(diào)度等各個(gè)方面,都不容易。于是,人們迫切需要一套管理系統(tǒng),對(duì)Docker及容器進(jìn)行更高級(jí)更靈活的管理。
K8s架構(gòu)

Kubernetes 中部署的最小單位是 pod,而不是 Docker 容器。實(shí)現(xiàn)上 Kubernetes 是不依賴于 Docker 的,完全可以使用其他的容器引擎在 Kubernetes 管理的集群中替代 Docker。在與 Docker 結(jié)合使用時(shí),一個(gè) pod 中可以包含一個(gè)或多個(gè) Docker 容器。但除了有緊密耦合的情況下,通常一個(gè) pod 中只有一個(gè)容器,這樣方便不同的服務(wù)各自獨(dú)立地?cái)U(kuò)展。
使用Docker&K8S
Docker安裝
首先快速地介紹一下 Docker:作為示例,我們?cè)诒镜貑?dòng) Docker 的守護(hù)進(jìn)程,并在一個(gè)容器里運(yùn)行簡(jiǎn)單的 HTTP 服務(wù)。先完成安裝:
$ brew cask install docker
上面的命令會(huì)從 Homebrew 安裝 Docker for Mac,它包含 Docker 的后臺(tái)進(jìn)程和命令行工具。Docker 的后臺(tái)進(jìn)程以一個(gè) Mac App 的形式安裝在 /Applications 里,需要手動(dòng)啟動(dòng)。啟動(dòng) Docker 應(yīng)用后,可以在 Terminal 里確認(rèn)一下命令行工具的版本:
$ docker --version
Docker version 18.03.1-ce, build 9ee9f40
上面顯示的 Docker 版本可能和我的不一樣,但只要不是太老就好。
編寫(xiě)Dockerfile
我們建一個(gè)單獨(dú)的目錄來(lái)存放示例所需的文件。為了盡量簡(jiǎn)化例子,我們要部署的服務(wù)是用 Nginx 來(lái) serve 一個(gè)簡(jiǎn)單的 HTML 文件 html/index.html。
$ mkdir docker-demo
$ cd docker-demo
$ mkdir html
$ echo '<h1>Hello Docker!</h1>' > html/index.html
接下來(lái)在當(dāng)前目錄創(chuàng)建一個(gè)叫 Dockerfile 的新文件,包含下面的內(nèi)容:
FROM nginx
COPY html/* /usr/share/nginx/html
每個(gè) Dockerfile 都以 FROM ... 開(kāi)頭。FROM nginx 的意思是以 Nginx 官方提供的鏡像為基礎(chǔ)來(lái)構(gòu)建我們的鏡像。在構(gòu)建時(shí),Docker 會(huì)從 Docker Hub 查找和下載需要的鏡像。Docker Hub 對(duì)于 Docker 鏡像的作用就像 GitHub 對(duì)于代碼的作用一樣,它是一個(gè)托管和共享鏡像的服務(wù)。使用過(guò)和構(gòu)建的鏡像都會(huì)被緩存在本地。第二行把我們的靜態(tài)文件復(fù)制到鏡像的 /usr/share/nginx/html 目錄下。也就是 Nginx 尋找靜態(tài)文件的目錄。Dockerfile 包含構(gòu)建鏡像的指令,更詳細(xì)的信息可以參考這里。
Dockerfile編寫(xiě)可以參考https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
構(gòu)建Docker鏡像
$ docker build -t docker-demo:0.1 .
請(qǐng)確保你按照上面的步驟為這個(gè)實(shí)驗(yàn)新建了目錄,并且在這個(gè)目錄中運(yùn)行 docker build。如果你在其它有很多文件的目錄(比如你的用戶目錄或者 /tmp )運(yùn)行,docker 會(huì)把當(dāng)前目錄的所有文件作為上下文發(fā)送給負(fù)責(zé)構(gòu)建的后臺(tái)進(jìn)程。
這行命令中的名稱 docker-demo 可以理解為這個(gè)鏡像對(duì)應(yīng)的應(yīng)用名或服務(wù)名,0.1 是標(biāo)簽。Docker 通過(guò)名稱和標(biāo)簽的組合來(lái)標(biāo)識(shí)鏡像。
查看Docker鏡像
可以用下面的命令來(lái)看到剛剛創(chuàng)建的鏡像:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-demo 0.1 efb8ca048d5a 5 minutes ago 109MB
運(yùn)行Docker鏡像
下面我們把這個(gè)鏡像運(yùn)行起來(lái)。Nginx 默認(rèn)監(jiān)聽(tīng)在 80 端口,所以我們把宿主機(jī)的 8080 端口映射到容器的 80 端口:
$ docker run --name docker-demo -d -p 8080:80 docker-demo:0.1
查看容器運(yùn)行狀態(tài)
用下面的命令可以看到正在運(yùn)行中的容器:
$ docker container ps
CONTAINER ID IMAGE ... PORTS NAMES
c495a7ccf1c7 docker-demo:0.1 ... 0.0.0.0:8080->80/tcp docker-demo
這時(shí)如果你用瀏覽器訪問(wèn) http://localhost:8080,就能看到我們剛才創(chuàng)建的「Hello Docker!」頁(yè)面。
在現(xiàn)實(shí)的生產(chǎn)環(huán)境中 Docker 本身是一個(gè)相對(duì)底層的容器引擎,在有很多服務(wù)器的集群中,不太可能以上面的方式來(lái)管理任務(wù)和資源。所以我們需要 Kubernetes 這樣的系統(tǒng)來(lái)進(jìn)行任務(wù)的編排和調(diào)度。在進(jìn)入下一步前,別忘了把實(shí)驗(yàn)用的容器清理掉:
$ docker container stop docker-demo
$ docker container rm docker-demo
==============
安裝 Kubernetes
介紹完 Docker,終于可以開(kāi)始試試 Kubernetes 了。我們需要安裝三樣?xùn)|西:Kubernetes 的命令行客戶端 kubctl、一個(gè)可以在本地跑起來(lái)的 Kubernetes 環(huán)境 Minikube、以及給 Minikube 使用的虛擬化引擎 xhyve。
$ brew install kubectl
$ brew cask install minikube
$ brew install docker-machine-driver-xhyve
Minikube 默認(rèn)的虛擬化引擎是 VirtualBox,而 xhyve 是一個(gè)更輕量、性能更好的替代。它需要以 root 權(quán)限運(yùn)行,所以安裝完要把所有者改為 root:wheel,并把 setuid 權(quán)限打開(kāi):
$ sudo chown root:wheel /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
$ sudo chmod u+s /usr/local/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
然后就可以啟動(dòng) Minikube 了:
$ minikube start --vm-driver xhyve
你多半會(huì)看到一個(gè)警告說(shuō) xhyve 會(huì)在未來(lái)的版本被 hyperkit 替代,推薦使用 hyperkit。不過(guò)在我寫(xiě)這個(gè)教程的時(shí)候 docker-machine-driver-hyperkit 還沒(méi)有進(jìn)入 Homebrew, 需要手動(dòng)編譯和安裝,我就偷個(gè)懶,仍然用 xhyve。以后只要在安裝和運(yùn)行的命令中把 xhyve 改為 hyperkit 就可以。
如果你在第一次啟動(dòng) Minikube 時(shí)遇到錯(cuò)誤或被中斷,后面重試仍然失敗時(shí),可以嘗試運(yùn)行 minikube delete 把集群刪除,重新來(lái)過(guò)。
Minikube 啟動(dòng)時(shí)會(huì)自動(dòng)配置 kubectl,把它指向 Minikube 提供的 Kubernetes API 服務(wù)??梢杂孟旅娴拿畲_認(rèn):
$ kubectl config current-context
minikube
Kubernetes 架構(gòu)簡(jiǎn)介
典型的 Kubernetes 集群包含一個(gè) master 和很多 node。Master 是控制集群的中心,node 是提供 CPU、內(nèi)存和存儲(chǔ)資源的節(jié)點(diǎn)。Master 上運(yùn)行著多個(gè)進(jìn)程,包括面向用戶的 API 服務(wù)、負(fù)責(zé)維護(hù)集群狀態(tài)的 Controller Manager、負(fù)責(zé)調(diào)度任務(wù)的 Scheduler 等。每個(gè) node 上運(yùn)行著維護(hù) node 狀態(tài)并和 master 通信的 kubelet,以及實(shí)現(xiàn)集群網(wǎng)絡(luò)服務(wù)的 kube-proxy。
作為一個(gè)開(kāi)發(fā)和測(cè)試的環(huán)境,Minikube 會(huì)建立一個(gè)有一個(gè) node 的集群,用下面的命令可以看到:
$ kubectl get nodes
NAME STATUS AGE VERSION
minikube Ready 1h v1.10.0
部署一個(gè)單實(shí)例服務(wù)
我們先嘗試像文章開(kāi)始介紹 Docker 時(shí)一樣,部署一個(gè)簡(jiǎn)單的服務(wù)。Kubernetes 中部署的最小單位是 pod,而不是 Docker 容器。實(shí)時(shí)上 Kubernetes 是不依賴于 Docker 的,完全可以使用其他的容器引擎在 Kubernetes 管理的集群中替代 Docker。在與 Docker 結(jié)合使用時(shí),一個(gè) pod 中可以包含一個(gè)或多個(gè) Docker 容器。但除了有緊密耦合的情況下,通常一個(gè) pod 中只有一個(gè)容器,這樣方便不同的服務(wù)各自獨(dú)立地?cái)U(kuò)展。
Minikube 自帶了 Docker 引擎,所以我們需要重新配置客戶端,讓 docker 命令行與 Minikube 中的 Docker 進(jìn)程通訊:
$ eval $(minikube docker-env)
在運(yùn)行上面的命令后,再運(yùn)行 docker image ls 時(shí)只能看到一些 Minikube 自帶的鏡像,就看不到我們剛才構(gòu)建的 docker-demo:0.1 鏡像了。所以在繼續(xù)之前,要重新構(gòu)建一遍我們的鏡像,這里順便改一下名字,叫它 k8s-demo:0.1。
$ docker build -t k8s-demo:0.1 .
創(chuàng)建Pod定義文件
然后創(chuàng)建一個(gè)叫 pod.yml 的定義文件:
apiVersion: v1
kind: Pod
metadata:
name: k8s-demo
spec:
containers:
- name: k8s-demo
image: k8s-demo:0.1
ports:
- containerPort: 80
這里定義了一個(gè)叫 k8s-demo 的 Pod,使用我們剛才構(gòu)建的 k8s-demo:0.1 鏡像。這個(gè)文件也告訴 Kubernetes 容器內(nèi)的進(jìn)程會(huì)監(jiān)聽(tīng) 80 端口。然后把它跑起來(lái):
$ kubectl create -f pod.yml
pod "k8s-demo" created
kubectl 把這個(gè)文件提交給 Kubernetes API 服務(wù),然后 Kubernetes Master 會(huì)按照要求把 Pod 分配到 node 上。用下面的命令可以看到這個(gè)新建的 Pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
k8s-demo 1/1 Running 0 5s
因?yàn)槲覀兊溺R像在本地,并且這個(gè)服務(wù)也很簡(jiǎn)單,所以運(yùn)行 kubectl get pods 的時(shí)候 STATUS 已經(jīng)是 running。要是使用遠(yuǎn)程鏡像(比如 Docker Hub 上的鏡像),你看到的狀態(tài)可能不是 Running,就需要再等待一下。
雖然這個(gè) pod 在運(yùn)行,但是我們是無(wú)法像之前測(cè)試 Docker 時(shí)一樣用瀏覽器訪問(wèn)它運(yùn)行的服務(wù)的??梢岳斫鉃?pod 都運(yùn)行在一個(gè)內(nèi)網(wǎng),我們無(wú)法從外部直接訪問(wèn)。要把服務(wù)暴露出來(lái),我們需要?jiǎng)?chuàng)建一個(gè) Service。Service 的作用有點(diǎn)像建立了一個(gè)反向代理和負(fù)載均衡器,負(fù)責(zé)把請(qǐng)求分發(fā)給后面的 pod。
創(chuàng)建Service定義文件(類似于反向代理)
創(chuàng)建一個(gè) Service 的定義文件 svc.yml:
apiVersion: v1
kind: Service
metadata:
name: k8s-demo-svc
labels:
app: k8s-demo
spec:
type: NodePort
ports:
- port: 80
nodePort: 30050
selector:
app: k8s-demo
這個(gè) service 會(huì)把容器的 80 端口從 node 的 30050 端口暴露出來(lái)。注意文件最后兩行的 selector 部分,這里決定了請(qǐng)求會(huì)被發(fā)送給集群里的哪些 pod。這里的定義是所有包含「app: k8s-demo」這個(gè)標(biāo)簽的 pod。然而我們之前部署的 pod 并沒(méi)有設(shè)置標(biāo)簽:
$ kubectl describe pods | grep Labels
Labels: <none>
所以要先更新一下 pod.yml,把標(biāo)簽加上(注意在 metadata: 下增加了 labels 部分):
apiVersion: v1
kind: Pod
metadata:
name: k8s-demo
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo
image: k8s-demo:0.1
ports:
- containerPort: 80
然后更新 pod 并確認(rèn)成功新增了標(biāo)簽:
$ kubectl apply -f pod.yml
pod "k8s-demo" configured
$ kubectl describe pods | grep Labels
Labels: app=k8s-demo
然后就可以創(chuàng)建這個(gè) service 了:
$ kubectl create -f svc.yml
service "k8s-demo-svc" created
用下面的命令可以得到暴露出來(lái)的 URL,在瀏覽器里訪問(wèn),就能看到我們之前創(chuàng)建的網(wǎng)頁(yè)了。
$ minikube service k8s-demo-svc --url
http://192.168.64.4:30050
橫向擴(kuò)展、滾動(dòng)更新、版本回滾
在這一節(jié),我們來(lái)實(shí)驗(yàn)一下在一個(gè)高可用服務(wù)的生產(chǎn)環(huán)境會(huì)常用到的一些操作。在繼續(xù)之前,先把剛才部署的 pod 刪除(但是保留 service,下面還會(huì)用到):
$ kubectl delete pod k8s-demo
pod "k8s-demo" deleted
在正式環(huán)境中我們需要讓一個(gè)服務(wù)不受單個(gè)節(jié)點(diǎn)故障的影響,并且還要根據(jù)負(fù)載變化動(dòng)態(tài)調(diào)整節(jié)點(diǎn)數(shù)量,所以不可能像上面一樣逐個(gè)管理 pod。Kubernetes 的用戶通常是用 Deployment 來(lái)管理服務(wù)的。一個(gè) deployment 可以創(chuàng)建指定數(shù)量的 pod 部署到各個(gè) node 上,并可完成更新、回滾等操作。
創(chuàng)建Deployment定義文件
首先我們創(chuàng)建一個(gè)定義文件 deployment.yml:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: k8s-demo-deployment
spec:
replicas: 10
template:
metadata:
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo-pod
image: k8s-demo:0.1
ports:
- containerPort: 80
注意開(kāi)始的 apiVersion 和之前不一樣,因?yàn)?Deployment API 沒(méi)有包含在 v1 里,replicas: 10 指定了這個(gè) deployment 要有 10 個(gè) pod,后面的部分和之前的 pod 定義類似。提交這個(gè)文件,創(chuàng)建一個(gè) deployment:
$ kubectl create -f deployment.yml
deployment "k8s-demo-deployment" created
用下面的命令可以看到這個(gè) deployment 的副本集(replica set),有 10 個(gè) pod 在運(yùn)行。
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
k8s-demo-deployment-774878f86f 10 10 10 19s
假設(shè)我們對(duì)項(xiàng)目做了一些改動(dòng),要發(fā)布一個(gè)新版本。這里作為示例,我們只把 HTML 文件的內(nèi)容改一下, 然后構(gòu)建一個(gè)新版鏡像 k8s-demo:0.2:
$ echo '<h1>Hello Kubernetes!</h1>' > html/index.html
$ docker build -t k8s-demo:0.2 .
然后更新 deployment.yml:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: k8s-demo-deployment
spec:
replicas: 10
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: k8s-demo
spec:
containers:
- name: k8s-demo-pod
image: k8s-demo:0.2
ports:
- containerPort: 80
這里有兩個(gè)改動(dòng),第一個(gè)是更新了鏡像版本號(hào) image: k8s-demo:0.2,第二是增加了 minReadySeconds: 10 和 strategy 部分。新增的部分定義了更新策略:minReadySeconds: 10 指在更新了一個(gè) pod 后,需要在它進(jìn)入正常狀態(tài)后 10 秒再更新下一個(gè) pod;maxUnavailable: 1 指同時(shí)處于不可用狀態(tài)的 pod 不能超過(guò)一個(gè);maxSurge: 1 指多余的 pod 不能超過(guò)一個(gè)。這樣 Kubernetes 就會(huì)逐個(gè)替換 service 后面的 pod。運(yùn)行下面的命令開(kāi)始更新:
$ kubectl apply -f deployment.yml --record=true
deployment "k8s-demo-deployment" configured
這里的 --record=true 讓 Kubernetes 把這行命令記到發(fā)布?xì)v史中備查。這時(shí)可以馬上運(yùn)行下面的命令查看各個(gè) pod 的狀態(tài):
$ kubectl get pods
NAME READY STATUS ... AGE
k8s-demo-deployment-774878f86f-5wnf4 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-6kgjp 0/1 Terminating ... 7m
k8s-demo-deployment-774878f86f-8wpd8 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-hpmc5 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-rd5xw 1/1 Running ... 7m
k8s-demo-deployment-774878f86f-wsztw 1/1 Running ... 7m
k8s-demo-deployment-86dbd79ff6-7xcxg 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-bmvd7 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-hsjx5 1/1 Running ... 26s
k8s-demo-deployment-86dbd79ff6-mkn27 1/1 Running ... 14s
k8s-demo-deployment-86dbd79ff6-pkmlt 1/1 Running ... 1s
k8s-demo-deployment-86dbd79ff6-thh66 1/1 Running ... 26s
從 AGE 列就能看到有一部分 pod 是剛剛新建的,有的 pod 則還是老的。下面的命令可以顯示發(fā)布的實(shí)時(shí)狀態(tài):
$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 1 old replicas are pending termination...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out
由于我輸入得比較晚,發(fā)布已經(jīng)快要結(jié)束,所以只有三行輸出。下面的命令可以查看發(fā)布?xì)v史,因?yàn)榈诙伟l(fā)布使用了 --record=true 所以可以看到用于發(fā)布的命令。
$ kubectl rollout history deployment k8s-demo-deployment
deployments "k8s-demo-deployment"
REVISION CHANGE-CAUSE
1 <none>
2 kubectl apply --filename=deploy.yml --record=true
這時(shí)如果刷新瀏覽器,就可以看到更新的內(nèi)容「Hello Kubernetes!」。假設(shè)新版發(fā)布后,我們發(fā)現(xiàn)有嚴(yán)重的 bug,需要馬上回滾到上個(gè)版本,可以用這個(gè)很簡(jiǎn)單的操作:
$ kubectl rollout undo deployment k8s-demo-deployment --to-revision=1
deployment "k8s-demo-deployment" rolled back
Kubernetes 會(huì)按照既定的策略替換各個(gè) pod,與發(fā)布新版本類似,只是這次是用老版本替換新版本:
$ kubectl rollout status deployment k8s-demo-deployment
Waiting for rollout to finish: 4 out of 10 new replicas have been updated...
Waiting for rollout to finish: 6 out of 10 new replicas have been updated...
Waiting for rollout to finish: 8 out of 10 new replicas have been updated...
Waiting for rollout to finish: 1 old replicas are pending termination...
deployment "k8s-demo-deployment" successfully rolled out
在回滾結(jié)束之后,刷新瀏覽器就可以確認(rèn)網(wǎng)頁(yè)內(nèi)容又改回了「Hello Docker!」。
參考資料