1. 程式人生 > 其它 >實驗四-函式與異常處理的應用程式設計

實驗四-函式與異常處理的應用程式設計

YAML 檔案基本語法格式

在 Docker 環境下面我們是直接通過命令 docker run 來執行我們的應用的,在 Kubernetes 環境下面我們同樣也可以用類似 kubectl run 這樣的命令來執行我們的應用,但是在 Kubernetes 中卻是不推薦使用命令列的方式,而是希望使用我們稱為資源清單的東西來描述應用,資源清單可以用 YAML 或者 JSON 檔案來編寫,一般來說 YAML 檔案更方便閱讀和理解,所以我們的課程中都會使用 YAML 檔案來進行描述。

通過一個資源清單檔案來定義好一個應用後,我們就可以通過 kubectl 工具來直接執行它:

$ kubectl create -f xxxx.yaml

我們知道 kubectl 是直接操作 APIServer 的,所以就相當於把我們的清單提交給了 APIServer,然後叢集獲取到清單描述的應用資訊後存入到 etcd 資料庫中,然後 kube-scheduler 元件發現這個時候有一個 Pod 還沒有繫結到節點上,就會對這個 Pod 進行一系列的排程,把它排程到一個最合適的節點上,然後把這個節點和 Pod 繫結到一起(寫回到 etcd),然後節點上的 kubelet 元件這個時候 watch 到有一個 Pod 被分配過來了,就去把這個 Pod 的資訊拉取下來,然後根據描述通過容器執行時把容器創建出來,最後當然同樣把 Pod 狀態再寫回到 etcd 中去,這樣就完成了一整個的建立流程。

第一個容器化應用

比如現在我們通過 YAML 檔案編寫了一個如下的資源清單,命名為 nginx-deployment.yaml:

apiVersion: apps/v1  # API版本
kind: Deployment  # API物件型別
metadata:
  name: nginx-deploy
  labels:
    chapter: first-app
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2  # Pod 副本數量
  template:  # Pod 模板
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

然後直接用 kubectl 命令來建立這個應用:

$ kubectl create -f nginx-deployment.yaml
deployment.apps/nginx-deploy created
$ kubectl get pods
NAME                            READY   STATUS    RESTARTS   AGE
nginx-deploy-54f57cf6bf-2fdjz   1/1     Running   0          7s
nginx-deploy-54f57cf6bf-57287   1/1     Running   0          7s

我們可以看到會在叢集中生成兩個 Pod 出來。而整個資源清單檔案對應到 Kubernetes 中就是一個 API Object(API 物件),我們按照這些物件的要求填充上對應的屬性後,提交給 Kubernetes 叢集,就可以為我們創建出對應的資源物件,比如我們這裡定義的是一個 Deployment 型別的 API 物件,我們按照這個 API 物件的要求填充了一些屬性,就會為我們創建出對應的資源物件,我們可以通過下面的命令來獲取:

$ kubectl get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deploy   2/2     2            2           12m

Deployment 這個資源物件就是用來定義多副本應用的物件,而且還支援對每個副本進行滾動更新,上面我們的資源清單中的描述中有一個屬性 replicas: 2,所以最後生成兩個副本的 Pod。

而這個 Deployment 定義的副本 Pod 具體是什麼樣的,是通過下面的 Pod 模板來定義的,就是 template 下面的定義,這個模板中定義了我們的 Pod 中只有一個名為 nginx 的容器,容器使用的映象是 nginx:1.7.9(spec.containers[0].image),並且這個容器監聽的埠是 80(spec.containers[0].ports[0].containerPort),另外我們還為 Pod 添加了一個app: nginx這樣的 Label 標籤,這裡需要非常注意的是上面的 selector.matchLabels 區域就是來表示我們的 Deployment 來管理哪些 Pod 的,所以這個地方需要和 Pod 模板中的 Label 標籤保持一致,這個 Label 標籤之前我們也提到過是非常重要的。

另外我們也可以發現每個 API 物件都有一個 Metadata 的欄位,用來表示該物件的元資料的,比如定義 name、namespace 等,比如上面 Deployment 和 Pod 模板中都有這個欄位,至於為什麼 Pod 模板中沒有 name 這個元資訊呢,這是因為 Deployment 這個控制器會自動在他自己的 name 基礎上生成 Pod 名,不過 Deployment 下面定義的 Label 標籤就沒有 Pod 中定義的 Label 標籤那麼重要了,只是起到一個對該物件標識和過濾的作用。比如我們在查詢物件的時候可以帶上標籤來進行過濾:

$ kubectl get deployment -l chapter=first-app
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deploy   2/2     2            2           51m
$ kubectl get pods -l app=nginx
NAME                            READY   STATUS    RESTARTS   AGE
nginx-deploy-54f57cf6bf-2fdjz   1/1     Running   0          51m
nginx-deploy-54f57cf6bf-57287   1/1     Running   0          51m

到這裡我們就完成了我們的第一個應用的容器化部署,但是往往我們在部署應用的過程中或多或少會遇到一些問題,這個時候我們可以使用一個 kubectl describe 命令來檢視資源物件的詳細資訊,比如我們用下面的命令來檢視 Pod 的詳細資訊:

$ kubectl describe pod nginx-deploy-54f57cf6bf-2fdjz
Name:         nginx-deploy-54f57cf6bf-2fdjz
Namespace:    default
Priority:     0
Node:         ydzs-node2/10.151.30.23
Start Time:   Sat, 09 Nov 2019 15:20:32 +0800
Labels:       app=nginx
              pod-template-hash=54f57cf6bf
Annotations:  <none>
Status:       Running
IP:           10.244.2.199
IPs:
  IP:           10.244.2.199
Controlled By:  ReplicaSet/nginx-deploy-54f57cf6bf
Containers:
  nginx:
    Container ID:   docker://e40e78eee7a431b6e7277b414967cce936ac750e2a8ba30298302cdd89e54300
    Image:          nginx:1.7.9
    Image ID:       docker-pullable://nginx@sha256:e3456c851a152494c3e4ff5fcc26f240206abac0c9d794affb40e0714846c451
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sat, 09 Nov 2019 15:20:35 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-5tsh4 (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  default-token-5tsh4:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-5tsh4
    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  <unknown>  default-scheduler    Successfully assigned default/nginx-deploy-54f57cf6bf-2fdjz to ydzs-node2
  Normal  Pulled     52m        kubelet, ydzs-node2  Container image "nginx:1.7.9" already present on machine
  Normal  Created    52m        kubelet, ydzs-node2  Created container nginx
  Normal  Started    52m        kubelet, ydzs-node2  Started container nginx

我們可以看到看到很多這個 Pod 的詳細資訊,比如排程到的節點、狀態、IP 等,一般我們比較關心的是下面的 Events 部分,就是我們說的事件。

在 Kubernetes 建立資源物件的過程中,對該物件的一些重要操作,都會被記錄在這個物件的 Events 裡面,可以通過 kubectl describe 指令檢視對應的結果。所以這個指令也會是以後我們排錯過程中會經常使用的命令,一定要記住這個重要的命令。比如上面我們描述的這個 Pod,我們可以看到它被建立之後,被排程器排程(Successfully assigned)到了 ydzs-node2 節點上,然後指定的映象已經在該節點上存在了,所以沒有再去拉取映象,然後建立我們定義的 nginx 容器,最後啟動定義的容器。

另外一個方面如果我們相對我們的應用進行升級的話應該怎麼辦呢?這個操作在我們日常工作中還是非常常見的,而在 Kubernetes 這裡也是非常簡單的,我們只需要修改我們的資源清單檔案即可,比如我們把映象升級到最新版本nginx:latest:

...    
    spec:
      containers:
      - name: nginx
        image: nginx:latest  # 這裡被從 1.7.9 修改為latest
        ports:
      - containerPort: 80

然後我們可以通過kubectl apply命令來直接更新,這個命令也是推薦我們使用的,我們不必關心當前的操作是建立,還是更新,執行的命令始終是 kubectl apply,Kubernetes 則會根據 YAML 檔案的內容變化,自動進行具體的處理,所以無論是建立還是更新都可以直接使用這個命令:

$ kubectl apply -f nginx-deployment.yaml

通過這個命令就可以來更新我們的應用了,由於我們這裡使用的是一個 Deployment 的控制器,所以會滾動更新我們的應用,我們可以通過在命令後面加上 --watch 引數來檢視 Pod 的更新過程:

$ kubectl get pods -l app=nginx --watch
NAME                            READY   STATUS              RESTARTS   AGE
nginx-deploy-54f57cf6bf-2fdjz   1/1     Terminating         0          72m
nginx-deploy-54f57cf6bf-57287   1/1     Running             0          72m
nginx-deploy-786b576769-69lrl   1/1     Running             0          4s
nginx-deploy-786b576769-wwmvz   0/1     ContainerCreating   0          2s
nginx-deploy-54f57cf6bf-2fdjz   0/1     Terminating         0          72m
nginx-deploy-786b576769-wwmvz   1/1     Running             0          3s
nginx-deploy-54f57cf6bf-57287   1/1     Terminating         0          72m
nginx-deploy-54f57cf6bf-57287   0/1     Terminating         0          72m
nginx-deploy-54f57cf6bf-57287   0/1     Terminating         0          72m
nginx-deploy-54f57cf6bf-57287   0/1     Terminating         0          72m
nginx-deploy-54f57cf6bf-2fdjz   0/1     Terminating         0          72m
nginx-deploy-54f57cf6bf-2fdjz   0/1     Terminating         0          72m

可以看到更新過程是先殺掉了一個 Pod,然後又重新建立了一個新的 Pod,然後又殺掉一箇舊的 Pod,再建立一個新的 Pod,這樣交替替換的,最後剩下兩個新的 Pod,這就是我們所說的滾動更新,滾動更新對於我們的應用持續提供服務是非常重要的手段,在日常工作中更新應用肯定會採用這種方式。

最後,如果需要把我們的應用從叢集中刪除掉,可以用 kubectl delete 命令來清理:

$ kubectl delete -f nginx-deployment.yaml

YAML 檔案

如何編寫資源清單檔案呢?日常使用的時候我們都是使用 YAML 檔案來編寫,但是現狀卻是大部分同學對 JSON 更加熟悉,對 YAML 檔案的格式不是很熟悉,所以也導致很多同學在編寫資源清單的時候似懂非懂的感覺,所以在瞭解如何編寫資源清單之前我們非常有必要來了解下 YAML 檔案的用法。

YAML 是專門用來寫配置檔案的語言,非常簡潔和強大,遠比 JSON 格式方便。YAML語言(發音 /ˈjæməl/)的設計目標,就是方便人類讀寫。它實質上是一種通用的資料序列化格式。

它的基本語法規則如下:

  • 大小寫敏感
  • 使用縮排表示層級關係
  • 縮排時不允許使用Tab鍵,只允許使用空格
  • 縮排的空格數目不重要,只要相同層級的元素左側對齊即可
  • # 表示註釋,從這個字元一直到行尾,都會被解析器忽略

在 Kubernetes 中,我們只需要瞭解兩種結構型別就行了:

  • Lists(列表)
  • Maps(字典)

也就是說,你可能會遇到 Lists 的 Maps 和 Maps 的 Lists,等等。不過不用擔心,你只要掌握了這兩種結構也就可以了,其他更加複雜的我們暫不討論。

Maps

首先我們來看看 Maps,我們都知道 Map 是字典,就是一個 key:value 的鍵值對,Maps 可以讓我們更加方便的去書寫配置資訊,例如:

---
apiVersion: v1
kind: Pod

第一行的---是分隔符,是可選的,在單一檔案中,可用連續三個連字號---區分多個檔案。這裡我們可以看到,我們有兩個鍵:kind 和 apiVersion,他們對應的值分別是:v1 和 Pod。上面的 YAML 檔案轉換成 JSON 格式的話,你肯定就容易明白了:

{
    "apiVersion": "v1",
    "kind": "pod"
}

我們在建立一個相對複雜一點的 YAML 檔案,建立一個 KEY 對應的值不是字串而是一個 Maps:

---
apiVersion: v1
kind: Pod
metadata:
  name: ydzs-site
  labels:
    app: web

上面的 YAML 檔案,metadata 這個 KEY 對應的值就是一個 Maps 了,而且巢狀的 labels 這個 KEY 的值又是一個 Map,你可以根據你自己的情況進行多層巢狀。

上面我們也提到了 YAML 檔案的語法規則,YAML 處理器是根據行縮排來知道內容之間的嗯關聯性的。比如我們上面的 YAML 檔案,我用了兩個空格作為縮排,空格的數量並不重要,但是你得保持一致,並且至少要求一個空格(什麼意思?就是你別一會縮排兩個空格,一會縮排4個空格)。我們可以看到 name 和 labels 是相同級別的縮排,所以 YAML 處理器就知道了他們屬於同一個 Map,而 app 是 labels 的值是因為 app 的縮排更大。

注意:在 YAML 檔案中絕對不要使用 tab 鍵來進行縮排。

同樣的,我們可以將上面的 YAML 檔案轉換成 JSON 檔案:

{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "kube100-site",
        "labels": {
            "app": "web"
        }
    }
}

或許你對上面的 JSON 檔案更熟悉,但是你不得不承認 YAML 檔案的語義化程度更高吧?

Lists

Lists就是列表,說白了就是陣列,在 YAML 檔案中我們可以這樣定義:

args
  - Cat
  - Dog
  - Fish

你可以有任何數量的項在列表中,每個項的定義以破折號(-)開頭的,與父元素之間可以縮排也可以不縮排。對應的 JSON 格式如下:

{
    "args": [ 'Cat', 'Dog', 'Fish' ]
}

當然,Lists 的子項也可以是 Maps,Maps 的子項也可以是 Lists 如下所示:

---
apiVersion: v1
kind: Pod
metadata:
  name: ydzs-site
  labels:
    app: web
spec:
  containers:
    - name: front-end
      image: nginx
      ports:
        - containerPort: 80
    - name: flaskapp-demo
      image: cnych/flaskapp
      ports:
        - containerPort: 5000

比如這個 YAML 檔案,我們定義了一個叫 containers 的 List 物件,每個子項都由 name、image、ports 組成,每個 ports 都有一個 key 為 containerPort 的 Map 組成,同樣的,我們可以轉成如下 JSON 格式檔案:

{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "ydzs-site",
        "labels": {
            "app": "web"
        }
    },
    "spec": {
        "containers": [{
            "name": "front-end",
            "image": "nginx",
            "ports": [{
                "containerPort": "80"
            }]
        }, {
            "name": "flaskapp-demo",
            "image": "cnych/flaskapp",
            "ports": [{
                "containerPort": "5000"
            }]
        }]
    }
}

是不是覺得用 JSON 格式的話檔案明顯比 YAML 檔案更復雜了呢?

如何編寫資源清單

上面我們瞭解了 YAML 檔案的基本語法,現在至少可以保證我們的編寫的 YAML 檔案語法是合法的,那麼要怎麼編寫符合 Kubernetes API 物件的資源清單呢?比如我們怎麼知道 Pod、Deployment 這些資源物件有哪些功能、有哪些欄位呢?

一些簡單的資源物件我們可能可以憑藉記憶寫出對應的資源清單,但是 Kubernetes 發展也非常快,版本迭代也很快,每個版本中資源物件可能又有很多變化,那麼有沒有一種辦法可以讓我們做到有的放矢呢?

實際上是有的,可以利用一些網站提供的現成yaml檔案

網站:https://k8syaml.com/,https://www.kubebiz.com/

但是如果平時我們編寫資源清單的時候都這樣去查詢文件勢必會效率低下,Kubernetes 也考慮到了這點,我們可以直接通過 kubectl 命令列工具來獲取這些欄位資訊,同樣的,比如我們要獲取 Deployment 的欄位資訊,我們可以通過 kubectl explain 命令來了解:

$ kubectl explain deployment
KIND:     Deployment
VERSION:  apps/v1

DESCRIPTION:
     Deployment enables declarative updates for Pods and ReplicaSets.

FIELDS:
   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

   metadata <Object>
     Standard object metadata.

   spec <Object>
     Specification of the desired behavior of the Deployment.

   status   <Object>
     Most recently observed status of the Deployment.

我們可以看到上面的資訊和我們在 API 文件中檢視到的基本一致,比如我們看到其中 spec 欄位是一個 <Object> 型別的,證明該欄位下面是一個物件,我們可以繼續去檢視這個欄位下面的詳細資訊:

$ kubectl explain deployment.spec
KIND:     Deployment
VERSION:  apps/v1

RESOURCE: spec <Object>

DESCRIPTION:
     Specification of the desired behavior of the Deployment.

     DeploymentSpec is the specification of the desired behavior of the
     Deployment.

FIELDS:
   minReadySeconds  <integer>
     Minimum number of seconds for which a newly created pod should be ready
     without any of its container crashing, for it to be considered available.
     Defaults to 0 (pod will be considered available as soon as it is ready)

   paused   <boolean>
     Indicates that the deployment is paused.

   progressDeadlineSeconds  <integer>
     The maximum time in seconds for a deployment to make progress before it is
     considered to be failed. The deployment controller will continue to process
     failed deployments and a condition with a ProgressDeadlineExceeded reason
     will be surfaced in the deployment status. Note that progress will not be
     estimated during the time a deployment is paused. Defaults to 600s.

   replicas <integer>
     Number of desired pods. This is a pointer to distinguish between explicit
     zero and not specified. Defaults to 1.

   revisionHistoryLimit <integer>
     The number of old ReplicaSets to retain to allow rollback. This is a
     pointer to distinguish between explicit zero and not specified. Defaults to
     10.

   selector <Object> -required-
     Label selector for pods. Existing ReplicaSets whose pods are selected by
     this will be the ones affected by this deployment. It must match the pod
     template's labels.

   strategy <Object>
     The deployment strategy to use to replace existing pods with new ones.

   template <Object> -required-
     Template describes the pods that will be created.

如果一個欄位顯示的是 required,這就證明該自動是必填的,也就是我們在建立這個資源物件的時候必須宣告這個欄位,每個欄位的型別也都完全為我們進行了說明,所以有了 kubectl explain 這個命令我們就完全可以寫出一個不熟悉的資源物件的清單說明了,這個命令我們也是必須要記住的,會在以後的工作中為我們提供很大的幫助。