Docker 和 Kubernetes 從聽過到略懂:給程序員的旋風教程

早在 Docker 正式發佈幾個月的時候,LeanCloud 就開始在生產環境大規模使用 Docker,在過去幾年裏 Docker 的技術棧支撐了咱們主要的後端架構。這是一篇寫給程序員的 Docker 和 Kubernetes 教程,目的是讓熟悉技術的讀者在儘量短的時間內對 Docker 和 Kubernetes 有基本的瞭解,並經過實際部署、升級、回滾一個服務體驗容器化生產環境的原理和好處。本文假設讀者都是開發者,並熟悉 Mac/Linux 環境,因此就不介紹基礎的技術概念了。命令行環境以 Mac 示例,在 Linux 下只要根據本身使用的發行版和包管理工具作調整便可。html

Docker 速成

首先快速地介紹一下 Docker:做爲示例,咱們在本地啓動 Docker 的守護進程,並在一個容器裏運行簡單的 HTTP 服務。先完成安裝:前端

$ brew cask install docker

上面的命令會從 Homebrew 安裝 Docker for Mac,它包含 Docker 的後臺進程和命令行工具。Docker 的後臺進程以一個 Mac App 的形式安裝在 /Applications 裏,須要手動啓動。啓動 Docker 應用後,能夠在 Terminal 裏確認一下命令行工具的版本:node

$ docker --version
Docker version 18.03.1-ce, build 9ee9f40

上面顯示的 Docker 版本可能和個人不同,但只要不是太老就好。咱們建一個單獨的目錄來存放示例所需的文件。爲了儘可能簡化例子,咱們要部署的服務是用 Nginx 來 serve 一個簡單的 HTML 文件 html/index.htmlnginx

$ mkdir docker-demo
$ cd docker-demo
$ mkdir html
$ echo '<h1>Hello Docker!</h1>' > html/index.html

接下來在當前目錄建立一個叫 Dockerfile 的新文件,包含下面的內容:程序員

FROM nginx
COPY html/* /usr/share/nginx/html

每一個 Dockerfile 都以 FROM ... 開頭。FROM nginx 的意思是以 Nginx 官方提供的鏡像爲基礎來構建咱們的鏡像。在構建時,Docker 會從 Docker Hub 查找和下載須要的鏡像。Docker Hub 對於 Docker 鏡像的做用就像 GitHub 對於代碼的做用同樣,它是一個託管和共享鏡像的服務。使用過和構建的鏡像都會被緩存在本地。第二行把咱們的靜態文件複製到鏡像的 /usr/share/nginx/html 目錄下。也就是 Nginx 尋找靜態文件的目錄。Dockerfile 包含構建鏡像的指令,更詳細的信息能夠參考這裏docker

而後就能夠構建鏡像了:後端

$ docker build -t docker-demo:0.1 .

請確保你按照上面的步驟爲這個實驗新建了目錄,而且在這個目錄中運行 docker build。若是你在其它有不少文件的目錄(好比你的用戶目錄或者 /tmp )運行,docker 會把當前目錄的全部文件做爲上下文發送給負責構建的後臺進程。api

這行命令中的名稱 docker-demo 能夠理解爲這個鏡像對應的應用名或服務名,0.1 是標籤。Docker 經過名稱和標籤的組合來標識鏡像。能夠用下面的命令來看到剛剛建立的鏡像:瀏覽器

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker-demo         0.1                 efb8ca048d5a        5 minutes ago       109MB

下面咱們把這個鏡像運行起來。Nginx 默認監聽在 80 端口,因此咱們把宿主機的 8080 端口映射到容器的 80 端口:緩存

$ docker run --name docker-demo -d -p 8080:80 docker-demo:0.1

用下面的命令能夠看到正在運行中的容器:

$ docker container ps
CONTAINER ID  IMAGE            ...  PORTS                 NAMES
c495a7ccf1c7  docker-demo:0.1  ...  0.0.0.0:8080->80/tcp  docker-demo

這時若是你用瀏覽器訪問 http://localhost:8080,就能看到咱們剛纔建立的「Hello Docker!」頁面。

在現實的生產環境中 Docker 自己是一個相對底層的容器引擎,在有不少服務器的集羣中,不太可能以上面的方式來管理任務和資源。因此咱們須要 Kubernetes 這樣的系統來進行任務的編排和調度。在進入下一步前,別忘了把實驗用的容器清理掉:

$ docker container stop docker-demo
$ docker container rm docker-demo

安裝 Kubernetes

介紹完 Docker,終於能夠開始試試 Kubernetes 了。咱們須要安裝三樣東西:Kubernetes 的命令行客戶端 kubctl、一個能夠在本地跑起來的 Kubernetes 環境 Minikube、以及給 Minikube 使用的虛擬化引擎 xhyve。

$ brew install kubectl
$ brew cask install minikube
$ brew install docker-machine-driver-xhyve

Minikube 默認的虛擬化引擎是 VirtualBox,而 xhyve 是一個更輕量、性能更好的替代。它須要以 root 權限運行,因此安裝完要把全部者改成 root:wheel,並把 setuid 權限打開:

$ 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

而後就能夠啓動 Minikube 了:

$ minikube start --vm-driver xhyve

你多半會看到一個警告說 xhyve 會在將來的版本被 hyperkit 替代,推薦使用 hyperkit。不過在我寫這個教程的時候 docker-machine-driver-hyperkit 尚未進入 Homebrew, 須要手動編譯和安裝,我就偷個懶,仍然用 xhyve。之後只要在安裝和運行的命令中把 xhyve 改成 hyperkit 就能夠。

若是你在第一次啓動 Minikube 時遇到錯誤或被中斷,後面重試仍然失敗時,能夠嘗試運行 minikube delete 把集羣刪除,從新來過。

Minikube 啓動時會自動配置 kubectl,把它指向 Minikube 提供的 Kubernetes API 服務。能夠用下面的命令確認:

$ kubectl config current-context
minikube

Kubernetes 架構簡介

典型的 Kubernetes 集羣包含一個 master 和不少 node。Master 是控制集羣的中心,node 是提供 CPU、內存和存儲資源的節點。Master 上運行着多個進程,包括面向用戶的 API 服務、負責維護集羣狀態的 Controller Manager、負責調度任務的 Scheduler 等。每一個 node 上運行着維護 node 狀態並和 master 通訊的 kubelet,以及實現集羣網絡服務的 kube-proxy。

做爲一個開發和測試的環境,Minikube 會創建一個有一個 node 的集羣,用下面的命令能夠看到:

$ kubectl get nodes
NAME       STATUS    AGE       VERSION
minikube   Ready     1h        v1.10.0

部署一個單實例服務

咱們先嚐試像文章開始介紹 Docker 時同樣,部署一個簡單的服務。Kubernetes 中部署的最小單位是 pod,而不是 Docker 容器。實時上 Kubernetes 是不依賴於 Docker 的,徹底可使用其餘的容器引擎在 Kubernetes 管理的集羣中替代 Docker。在與 Docker 結合使用時,一個 pod 中能夠包含一個或多個 Docker 容器。但除了有緊密耦合的狀況下,一般一個 pod 中只有一個容器,這樣方便不一樣的服務各自獨立地擴展。

Minikube 自帶了 Docker 引擎,因此咱們須要從新配置客戶端,讓 docker 命令行與 Minikube 中的 Docker 進程通信:

$ eval $(minikube docker-env)

在運行上面的命令後,再運行 docker image ls 時只能看到一些 Minikube 自帶的鏡像,就看不到咱們剛纔構建的 docker-demo:0.1 鏡像了。因此在繼續以前,要從新構建一遍咱們的鏡像,這裏順便改一下名字,叫它 k8s-demo:0.1。

$ docker build -t k8s-demo:0.1 .

而後建立一個叫 pod.yml 的定義文件:

apiVersion: v1
kind: Pod
metadata:
  name: k8s-demo
spec:
  containers:
    - name: k8s-demo
      image: k8s-demo:0.1
      ports:
        - containerPort: 80

這裏定義了一個叫 k8s-demo 的 Pod,使用咱們剛纔構建的 k8s-demo:0.1 鏡像。這個文件也告訴 Kubernetes 容器內的進程會監聽 80 端口。而後把它跑起來:

$ kubectl create -f pod.yml
pod "k8s-demo" created

kubectl 把這個文件提交給 Kubernetes API 服務,而後 Kubernetes Master 會按照要求把 Pod 分配到 node 上。用下面的命令能夠看到這個新建的 Pod:

$ kubectl get pods
NAME       READY     STATUS    RESTARTS   AGE
k8s-demo   1/1       Running   0          5s

由於咱們的鏡像在本地,而且這個服務也很簡單,因此運行 kubectl get pods 的時候 STATUS 已是 running。要是使用遠程鏡像(好比 Docker Hub 上的鏡像),你看到的狀態可能不是 Running,就須要再等待一下。

雖然這個 pod 在運行,可是咱們是沒法像以前測試 Docker 時同樣用瀏覽器訪問它運行的服務的。能夠理解爲 pod 都運行在一個內網,咱們沒法從外部直接訪問。要把服務暴露出來,咱們須要建立一個 Service。Service 的做用有點像創建了一個反向代理和負載均衡器,負責把請求分發給後面的 pod。

建立一個 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

這個 service 會把容器的 80 端口從 node 的 30050 端口暴露出來。注意文件最後兩行的 selector 部分,這裏決定了請求會被髮送給集羣裏的哪些 pod。這裏的定義是全部包含「app: k8s-demo」這個標籤的 pod。然而咱們以前部署的 pod 並無設置標籤:

$ kubectl describe pods | grep Labels
Labels:        <none>

因此要先更新一下 pod.yml,把標籤加上(注意在 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 並確認成功新增了標籤:

$ kubectl apply -f pod.yml
pod "k8s-demo" configured
$ kubectl describe pods | grep Labels
Labels:        app=k8s-demo

而後就能夠建立這個 service 了:

$ kubectl create -f svc.yml
service "k8s-demo-svc" created

用下面的命令能夠獲得暴露出來的 URL,在瀏覽器裏訪問,就能看到咱們以前建立的網頁了。

$ minikube service k8s-demo-svc --url
http://192.168.64.4:30050

橫向擴展、滾動更新、版本回滾

在這一節,咱們來實驗一下在一個高可用服務的生產環境會經常使用到的一些操做。在繼續以前,先把剛纔部署的 pod 刪除(可是保留 service,下面還會用到):

$ kubectl delete pod k8s-demo
pod "k8s-demo" deleted

在正式環境中咱們須要讓一個服務不受單個節點故障的影響,而且還要根據負載變化動態調整節點數量,因此不可能像上面同樣逐個管理 pod。Kubernetes 的用戶一般是用 Deployment 來管理服務的。一個 deployment 能夠建立指定數量的 pod 部署到各個 node 上,並可完成更新、回滾等操做。

首先咱們建立一個定義文件 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

注意開始的 apiVersion 和以前不同,由於 Deployment API 沒有包含在 v1 裏,replicas: 10 指定了這個 deployment 要有 10 個 pod,後面的部分和以前的 pod 定義相似。提交這個文件,建立一個 deployment:

$ kubectl create -f deployment.yml
deployment "k8s-demo-deployment" created

用下面的命令能夠看到這個 deployment 的副本集(replica set),有 10 個 pod 在運行。

$ kubectl get rs
NAME                             DESIRED   CURRENT   READY     AGE
k8s-demo-deployment-774878f86f   10        10        10        19s

假設咱們對項目作了一些改動,要發佈一個新版本。這裏做爲示例,咱們只把 HTML 文件的內容改一下, 而後構建一個新版鏡像 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

這裏有兩個改動,第一個是更新了鏡像版本號 image: k8s-demo:0.2,第二是增長了 minReadySeconds: 10strategy 部分。新增的部分定義了更新策略:minReadySeconds: 10 指在更新了一個 pod 後,須要在它進入正常狀態後 10 秒再更新下一個 pod;maxUnavailable: 1 指同時處於不可用狀態的 pod 不能超過一個;maxSurge: 1 指多餘的 pod 不能超過一個。這樣 Kubernetes 就會逐個替換 service 後面的 pod。運行下面的命令開始更新:

$ kubectl apply -f deployment.yml --record=true
deployment "k8s-demo-deployment" configured

這裏的 --record=true 讓 Kubernetes 把這行命令記到發佈歷史中備查。這時能夠立刻運行下面的命令查看各個 pod 的狀態:

$ 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 則仍是老的。下面的命令能夠顯示發佈的實時狀態:

$ 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

因爲我輸入得比較晚,發佈已經快要結束,因此只有三行輸出。下面的命令能夠查看發佈歷史,由於第二次發佈使用了 --record=true 因此能夠看到用於發佈的命令。

$ kubectl rollout history deployment k8s-demo-deployment
deployments "k8s-demo-deployment"
REVISION    CHANGE-CAUSE
1        <none>
2        kubectl apply --filename=deploy.yml --record=true

這時若是刷新瀏覽器,就能夠看到更新的內容「Hello Kubernetes!」。假設新版發佈後,咱們發現有嚴重的 bug,須要立刻回滾到上個版本,能夠用這個很簡單的操做:

$ kubectl rollout undo deployment k8s-demo-deployment --to-revision=1
deployment "k8s-demo-deployment" rolled back

Kubernetes 會按照既定的策略替換各個 pod,與發佈新版本相似,只是此次是用老版本替換新版本:

$ 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

在回滾結束以後,刷新瀏覽器就能夠確認網頁內容又改回了「Hello Docker!」。

結語

咱們從不一樣層面實踐了一遍鏡像的構建和容器的部署,而且部署了一個有 10 個容器的 deployment, 實驗了滾動更新和回滾的流程。Kubernetes 提供了很是多的功能,本文只是以蜻蜓點水的方式作了一個快節奏的 walkthrough,略過了不少細節。雖然你還不能在簡歷上加上「精通 Kubernetes」,可是應該能夠在本地的 Kubernetes 環境測試本身的先後端項目,遇到具體的問題時求助於 Google 和官方文檔便可。在此基礎上進一步熟悉應該就能夠在別人提供的 Kubernetes 生產環境發佈本身的服務。

LeanCloud 的大部分服務都運行在基於 Docker 的基礎設施上,包括各個 API 服務、中間件、後端任務等。大部分使用 LeanCloud 的開發者主要工做在前端,不過雲引擎是咱們的產品中讓容器技術離用戶最近的。雲引擎提供了容器帶來的隔離良好、擴容簡便等優勢,同時又直接支持各個語言的原生依賴管理,爲用戶免去了鏡像構建、監控、恢復等負擔,很適合但願把精力徹底投入在開發上的用戶。

LeanCloud 在招聘如下職位:

市場團隊負責人

後端軟件工程師(Clojure、Python、Java)

Android 軟件工程師

具體的需求以及其餘正在招聘的職位請見咱們的工做機會頁面。除了在官網上能夠看到的已經發布的產品外,咱們也在開發讓人興奮的新產品,有不少有意義、有價值的工做。

若是轉載本文,請包含原文連接和招聘信息。

相關文章
相關標籤/搜索