而在這篇文章中,我們就來扮演一個應用開發者的角色,使用這個 Kubernetes 集群發布第一個容器化應用。
在開始實踐之前,我先給你講解一下 Kubernetes 里面與開發者關系最密切的幾個概念。
作為一個應用開發者,你首先要做的,是制作容器的鏡像。而有了容器鏡像之后,你需要按照+Kubernetes+項目的規范和要求,將你的鏡像組織為它能夠“認識”的方式,然后提交上去。
那么,什么才是 Kubernetes 項目能“認識”的方式呢?
這就是使用 Kubernetes 的必備技能:編寫配置文件。
備注:這些配置文件可以是 YAML 或者 JSON 格式的。為方便閱讀與理解,在后面的講解中,我會統一使用 YAML 文件來指代它們。 Kubernetes 跟 Docker 等很多項目最大的不同,就在於它不推薦你使用命令行的方式直接運行容器(雖然 Kubernetes 項目也支持這種方式,比如:kubectl run),而是希望你用 YAML 文件的方式,即:把容器的定義、參數、配置,統統記錄在一個 YAML 文件中,然后用這樣一句指令把它運行起來:
$ kubectl create -f 我的配置文件
小案例 發布一個nginx應用
這么做最直接的好處是,你會有一個文件能記錄下 Kubernetes 到底“run”了什么。比如下面這個例子:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80
像這樣的一個 YAML 文件,對應到 kubernetes 中,就是一個 API Object(API 對象)。當你為這個對象的各個字段填好值並提交給 Kubernetes 之后,Kubernetes 就會負責創建出這些對象所定義的容器或者其他類型的 API 資源。
可以看到,這個 YAML 文件中的 Kind 字段,指定了這個 API 對象的類型(Type),是一個 Deployment。
所謂+Deployment,是一個定義多副本應用(即多個副本 Pod)的對象。此外,Deployment 還負責在 Pod 定義發生變化時,對每個副本進行滾動更新(Rolling+Update)。
在上面這個 YAML 文件中,我給它定義的 Pod 副本個數 (spec.replicas)是:2。
而這些 Pod 具體的又長什么樣子呢?
為此,我定義了一個 Pod 模版(spec.template),這個模版描述了我想要創建的 Pod 的細節。在上面的例子里,這個 Pod 里只有一個容器,這個容器的鏡像(spec.containers.image)是nginx=1.7.9,這個容器監聽端口(containerPort)是 80。
Pod 就是 Kubernetes 世界里的“應用”;而一個應用,可以由多個容器組成。
需要注意的是,像這樣使用一種 API 對象(Deployment)管理另一種 API 對象(Pod)的方法,在 Kubernetes 中,叫作“控制器”模式(controller pattern)。在我們的例子中,Deployment 扮演的正是 Pod 的控制器的角色。
你可能還注意到,這樣的每一個 API 對象都有一個叫作 Metadata 的字段,這個字段就是 API 對象的“標識”,即元數據,它也是我們從 Kubernetes 里找到這個對象的主要依據。這其中最主要使用到的字段是 Labels。
顧名思義,Labels 就是一組 key-value 格式的標簽。而像 Deployment 這樣的控制器對象,就可以通過這個 Labels 字段從 Kubernetes 中過濾出它所關心的被控制對象。
比如,在上面這個 YAML 文件中,Deployment 會把所有正在運行的、攜帶“app=nginx”標簽的 Pod 識別為被管理的對象,並確保這些 Pod 的總數嚴格等於兩個。
而這個過濾規則的定義,是在 Deployment 的“spec.selector.matchLabels”字段。我們一般稱之為:Label+Selector。
另外,在 Metadata 中,還有一個與 Labels 格式、層級完全相同的字段叫 Annotations,它專門用來攜帶 key-value 格式的內部信息。所謂內部信息,指的是對這些信息感興趣的,是 Kubernetes 組件本身,而不是用戶。所以大多數 Annotations,都是在 Kubernetes 運行過程中,被自動加在這個 API 對象上。
一個 Kubernetes 的 API 對象的定義,大多可以分為 Metadata 和 Spec 兩個部分。前者存放的是這個對象的元數據,對所有 API 對象來說,這一部分的字段和格式基本上是一樣的;而后者存放的,則是屬於這個對象獨有的定義,用來描述它所要表達的功能。
在了解了上述 Kubernetes 配置文件的基本知識之后,我們現在就可以把這個 YAML 文件“運行”起來。正如前所述,你可以使用 kubectl create 指令完成這個操作:
kubectl create -f nginx-deployment.yaml
然后,通過+kubectl+get+命令檢查這個 YAML 運行起來的狀態是不是與我們預期的一致:
kubectl get pods -l app=nginx
kubectl get 指令的作用,就是從 Kubernetes 里面獲取(GET)指定的 API 對象。可以看到,在這里我還加上了一個 -l 參數,即獲取所有匹配 app=nginx 標簽的 Pod。需要注意的是,在命令行中,所有 key-value 格式的參數,都使用“=”而非“:”表示。 從這條指令返回的結果中,我們可以看到現在有兩個 Pod 處於 Running 狀態,也就意味着我們這個 Deployment 所管理的 Pod 都處於預期的狀態。
此外, 你還可以使用 kubectl describe 命令,查看一個 API 對象的細節,比如:
kubectl describe pods nginx-deployment-5c678cfb6d-44pjt
詳細的輸出信息如下
Name: nginx-deployment-5c678cfb6d-44pjt Namespace: default Priority: 0 PriorityClassName: <none> Node: localhost.localdomain/10.104.204.21 Start Time: Mon, 18 Mar 2019 01:33:58 -0400 Labels: app=nginx pod-template-hash=1723479628 Annotations: <none> Status: Running IP: 10.32.0.13 Controlled By: ReplicaSet/nginx-deployment-5c678cfb6d Containers: nginx: Container ID: docker://8b93348544c4407829c670040e32dd5613e16037ad4ed444767bc7dd3dbbcc43 Image: nginx:1.8 Image ID: docker-pullable://nginx@sha256:c97ee70c4048fe79765f7c2ec0931957c2898f47400128f4f3640d0ae5d60d10 Port: 80/TCP Host Port: 0/TCP State: Running Started: Mon, 18 Mar 2019 01:33:58 -0400 Ready: True Restart Count: 0 Environment: <none> Mounts: /usr/share/nginx/html from nginx-vol (rw) /var/run/secrets/kubernetes.io/serviceaccount from default-token-9mzlh (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: nginx-vol: Type: EmptyDir (a temporary directory that shares a pod's lifetime) Medium: default-token-9mzlh: Type: Secret (a volume populated by a Secret) SecretName: default-token-9mzlh Optional: false QoS Class: BestEffort Node-Selectors: <none> Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 35m default-scheduler Successfully assigned default/nginx-deployment-5c678cfb6d-44pjt to localhost.localdomain Normal Pulled 35m kubelet, localhost.localdomain Container image "nginx:1.8" already present on machine Normal Created 35m kubelet, localhost.localdomain Created container Normal Started 35m kubelet, localhost.localdomain Started container
在 kubectl describe 命令返回的結果中,你可以清楚地看到這個 Pod 的詳細信息,比如它的 IP 地址等等。其中,有一個部分值得你特別關注,它就是Events(事件)。
在 Kubernetes 執行的過程中,對 API 對象的所有重要操作,都會被記錄在這個對象的 Events 里,並且顯示在 kubectl describe 指令返回的結果中。
比如,對於這個 Pod,我們可以看到它被創建之后,被調度器調度(Successfully assigned)到了 node-1,拉取了指定的鏡像(pulling image),然后啟動了 Pod 里定義的容器(Started container)。
所以,這個部分正是我們將來進行 Debug 的重要依據。如果有異常發生,你一定要第一時間查看這些Events,往往可以看到非常詳細的錯誤信息。
對nginx應用進行升級
接下來,如果我們要對這個 Nginx 服務進行升級,把它的鏡像版本從 1.7.9 升級為 1.8,要怎么做呢?
很簡單,我們只要修改這個 YAML 文件即可。
... spec: containers: - name: nginx image: nginx:1.8 # 這里被從 1.7.9 修改為 1.8 ports: - containerPort: 80
可是,這個修改目前只發生在本地,如何讓這個更新在 Kubernetes 里也生效呢? 我們可以使用+kubectl+replace+指令來完成這個更新:
kubectl replace -f nginx-deployment.yaml
不過,在本專欄里,我推薦你使用 kubectl apply 命令,來統一進行 Kubernetes 對象的創建和更新操作,具體做法如下所示:
kubectl apply -f nginx-deployment.yaml # 修改 nginx-deployment.yaml 的內容 kubectl apply -f nginx-deployment.yaml
這樣的操作方法,是 Kubernetes“聲明式 API”所推薦的使用方法。也就是說,作為用戶,你不必關心當前的操作是創建,還是更新,你執行的命令始終是 kubectl apply,而 Kubernetes 則會根據 YAML 文件的內容變化,自動進行具體的處理。
而這個流程的好處是,它有助於幫助開發和運維人員,圍繞着可以版本化管理的 YAML 文件,而不是“行蹤不定”的命令行進行協作,從而大大降低開發人員和運維人員之間的溝通成本。
舉個例子,一位開發人員開發好一個應用,制作好了容器鏡像。那么他就可以在應用的發布目錄里附帶上一個 Deployment 的 YAML 文件。
而運維人員,拿到這個應用的發布目錄后,就可以直接用這個 YAML 文件執行 kubectl apply 操作把它運行起來。
這時候,如果開發人員修改了應用,生成了新的發布內容,那么這個 YAML 文件,也就需要被修改,並且成為這次變更的一部分。
而接下來,運維人員可以使用 git diff 命令查看到這個 YAML 文件本身的變化,然后繼續用 kubectl apply 命令更新這個應用。
所以說,如果通過容器鏡像,我們能夠保證應用本身在開發與部署環境里的一致性的話,那么現在,Kubernetes 項目通過這些 YAML 文件,就保證了應用的“部署參數”在開發與部署環境中的一致性。
而當應用本身發生變化時,開發人員和運維人員可以依靠容器鏡像來進行同步;當應用部署參數發生變化時,這些 YAML 文件就是他們相互溝通和信任的媒介。
以上,就是 Kubernetes 發布應用的最基本操作了。
接下來,我們再在這個 Deployment 中嘗試聲明一個 Volume。在 Kubernetes 中,Volume 是屬於 Pod 對象的一部分。所以,我們就需要修改這個 YAML 文件里的 template.spec字段,如下所示:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.8 ports: - containerPort: 80 volumeMounts: - mountPath: "/usr/share/nginx/html" name: nginx-vol volumes: - name: nginx-vol emptyDir: {}
可以看到,我們在 Deployment 的 Pod 模板部分添加了一個 volumes 字段,定義了這個 Pod 聲明的所有 Volume。它的名字叫作 nginx-vol,類型是 emptyDir。
那什么是 emptyDir 類型呢?
它其實就等同於我們之前講過的 Docker 的隱式 Volume 參數,即:不顯式聲明宿主機目錄的 Volume。所以,Kubernetes 也會在宿主機上創建一個臨時目錄,這個目錄將來就會被綁定掛載到容器所聲明的 Volume 目錄上。
備注:不難看到,Kubernetes 的 emptyDir 類型,只是把 Kubernetes 創建的臨時目錄作為 Volume 的宿主機目錄,交給了 Docker。這么做的原因,是 Kubernetes 不想依賴 Docker 自己創建的那個 _data+目錄。
而 Pod 中的容器,使用的是 volumeMounts 字段來聲明自己要掛載哪個 Volume,並通過 mountPath 字段來定義容器內的 Volume 目錄,比如:/share/nginx/html。
當然,Kubernetes 也提供了顯式的 Volume 定義,它叫做 hostPath。比如下面的這個 YAML 文件:
... volumes: - name: nginx-vol hostPath: path: /var/data
這樣,容器 Volume 掛載的宿主機目錄,就變成了 /var/data。 在上述修改完成后,我們還是使用 kubectl apply 指令,更新這個 Deployment:
kubectl apply -f nginx-deployment.yaml
接下來,你可以通過 kubectl get 指令,查看兩個 Pod 被逐一更新的過程:
kubectl get pods
- NAME READY STATUS RESTARTS AGE
- nginx-deployment-5c678cfb6d-v5dlh 0/1 ContainerCreating 0 4s
- nginx-deployment-67594d6bf6-9gdvr 1/1 Running 0 10m
- nginx-deployment-67594d6bf6-v6j7w 1/1 Running 0 10m
kubectl get pods
- NAME READY STATUS RESTARTS AGE
- nginx-deployment-5c678cfb6d-lg9lw 1/1 Running 0 8s
- nginx-deployment-5c678cfb6d-v5dlh 1/1 Running 0 19s
從返回結果中,我們可以看到,新舊兩個 Pod,被交替創建、刪除,最后剩下的就是新版本的 Pod。這個滾動更新的過程,我也會在后續進行詳細的講解。
然后,你可以使用 kubectl describe 查看一下最新的 Pod,就會發現 Volume 的信息已經出現在了 Container 描述部分:
... Containers: nginx: Container ID: docker://07b4f89248791c2aa47787e3da3cc94b48576cd173018356a6ec8db2b6041343 Image: nginx:1.8 ... Environment: <none> Mounts: /usr/share/nginx/html from nginx-vol (rw) ... Volumes: nginx-vol: Type: EmptyDir (a temporary directory that shares a pod's lifetime)
最后,你還可以使用+kubectl+exec+指令,進入到這個+Pod+當中(即容器的+Namespace+中)查看這個+Volume+目錄:
kubectl exec -it nginx-deployment-5c678cfb6d-lg9lw -- /bin/bash ls /usr/share/nginx/html exit
此外,你想要從 Kubernetes 集群中刪除這個 Nginx Deployment 的話,直接執行:
kubectl delete -f nginx-deployment.yaml
備忘
部署和更新一個pod(應用)
kubectl apply -f ****.yaml
查看一個具體應用的名字和我們預期的是否一致
kubectl get pods -l app=name # 如 kubectl get pods -l app=nginx
查看一個 API 對象的細節,比如:
kubectl describe pods nginx-deployment-5c678cfb6d-44pjt
此外,你想要從 Kubernetes 集群中刪除這個 Nginx Deployment 的話,直接執行:
kubectl delete -f nginx-deployment.yaml