警告:實用警告

作為 Kubernetes 維護者,我們一直在尋找在保持相容性的同時提高可用性的方法。在我們開發功能、分類錯誤和回答支援問題時,我們累積了對 Kubernetes 使用者有幫助的資訊。過去,分享這些資訊的方式僅限於帶外方法,例如發行說明、公告電子郵件、文件和部落格文章。除非有人知道要尋找這些資訊並設法找到它,否則他們將無法從中受益。

在 Kubernetes v1.19 中,我們新增了一項功能,允許 Kubernetes API 伺服器向 API 客戶端發送警告。警告是使用標準 Warning 響應標頭發送的,因此它不會以任何方式更改狀態碼或響應正文。這允許伺服器發送任何 API 客戶端都易於讀取的警告,同時保持與先前客戶端版本的相容性。

kubectl v1.19+ 在 stderr 輸出中以及 k8s.io/client-go 客戶端程式庫 v0.19.0+ 在日誌輸出中顯示警告。k8s.io/client-go 行為可以按進程或按客戶端覆蓋

棄用警告

我們使用這項新功能的第一種方式是針對使用已棄用的 API 發送警告。

Kubernetes 是一個大型、快速發展的專案。跟上每個版本中的變更可能令人望而生畏,即使對於全職從事該專案的人來說也是如此。一種重要的變更類型是 API 棄用。隨著 Kubernetes 中的 API 升級到 GA 版本,預發佈 API 版本將被棄用並最終移除。

即使有延長的棄用期,並且棄用包含在發行說明中,它們仍然可能難以追蹤。在棄用期間,預發佈 API 保持功能正常,允許幾個版本過渡到穩定 API 版本。但是,我們發現使用者通常甚至沒有意識到他們依賴於已棄用的 API 版本,直到他們升級到停止提供該版本的發行版本。

從 v1.19 開始,每當向已棄用的 REST API 發出請求時,都會在 API 響應中返回警告。此警告包含有關不再提供 API 的發行版本以及替換 API 版本的詳細資訊。

由於警告源自伺服器,並在客戶端級別被攔截,因此它適用於所有 kubectl 命令,包括高階命令(如 kubectl apply)和低階命令(如 kubectl get --raw)。

kubectl applying a manifest file, then displaying a warning message 'networking.k8s.io/v1beta1 Ingress is deprecated in v1.19+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress'.

這有助於受棄用影響的人員了解他們發出的請求已棄用、他們需要多久才能解決問題以及他們應該改用哪個 API。當使用者應用他們沒有建立的 manifest 時,這尤其有幫助,因此他們有時間聯繫作者以請求更新的版本。

我們也意識到使用已棄用 API 的人通常與負責升級叢集的人不是同一個人,因此我們新增了兩個面向管理員的工具,以幫助追蹤已棄用 API 的使用情況並確定何時升級是安全的。

指標

從 Kubernetes v1.19 開始,當向已棄用的 REST API 端點發出請求時,kube-apiserver 進程中會將 apiserver_requested_deprecated_apis 儀表指標設定為 1。此指標具有 API groupversionresourcesubresource 的標籤,以及指示不再提供 API 的 Kubernetes 發行版本的 removed_release 標籤。

這是一個使用 kubectlprom2jsonjq 的範例查詢,以確定已從 API 伺服器的當前實例請求了哪些已棄用的 API。

kubectl get --raw /metrics | prom2json | jq '
  .[] | select(.name=="apiserver_requested_deprecated_apis").metrics[].labels
'

輸出

{
  "group": "extensions",
  "removed_release": "1.22",
  "resource": "ingresses",
  "subresource": "",
  "version": "v1beta1"
}
{
  "group": "rbac.authorization.k8s.io",
  "removed_release": "1.22",
  "resource": "clusterroles",
  "subresource": "",
  "version": "v1beta1"
}

這顯示已在此伺服器上請求了已棄用的 extensions/v1beta1 Ingress 和 rbac.authorization.k8s.io/v1beta1 ClusterRole API,並且將在 v1.22 中移除。

我們可以將該資訊與 apiserver_request_total 指標結合起來,以獲取有關向這些 API 發出的請求的更多詳細資訊。

kubectl get --raw /metrics | prom2json | jq '
  # set $deprecated to a list of deprecated APIs
  [
    .[] | 
    select(.name=="apiserver_requested_deprecated_apis").metrics[].labels |
    {group,version,resource}
  ] as $deprecated 
  
  |
  
  # select apiserver_request_total metrics which are deprecated
  .[] | select(.name=="apiserver_request_total").metrics[] |
  select(.labels | {group,version,resource} as $key | $deprecated | index($key))
'

輸出

{
  "labels": {
    "code": "0",
    "component": "apiserver",
    "contentType": "application/vnd.kubernetes.protobuf;stream=watch",
    "dry_run": "",
    "group": "extensions",
    "resource": "ingresses",
    "scope": "cluster",
    "subresource": "",
    "verb": "WATCH",
    "version": "v1beta1"
  },
  "value": "21"
}
{
  "labels": {
    "code": "200",
    "component": "apiserver",
    "contentType": "application/vnd.kubernetes.protobuf",
    "dry_run": "",
    "group": "extensions",
    "resource": "ingresses",
    "scope": "cluster",
    "subresource": "",
    "verb": "LIST",
    "version": "v1beta1"
  },
  "value": "1"
}
{
  "labels": {
    "code": "200",
    "component": "apiserver",
    "contentType": "application/json",
    "dry_run": "",
    "group": "rbac.authorization.k8s.io",
    "resource": "clusterroles",
    "scope": "cluster",
    "subresource": "",
    "verb": "LIST",
    "version": "v1beta1"
  },
  "value": "1"
}

輸出顯示僅向這些 API 發出讀取請求,並且向監看已棄用的 Ingress API 發出了最多的請求。

您也可以透過以下 Prometheus 查詢找到該資訊,該查詢返回有關向將在 v1.22 中移除的已棄用 API 發出的請求的資訊。

apiserver_requested_deprecated_apis{removed_release="1.22"} * on(group,version,resource,subresource)
group_right() apiserver_request_total

稽核註解

指標是檢查是否正在使用已棄用的 API 以及使用速率的快速方法,但它們不包含足夠的資訊來識別特定的客戶端或 API 物件。從 Kubernetes v1.19 開始,對已棄用 API 的請求的稽核事件包含 "k8s.io/deprecated":"true" 的稽核註解。管理員可以使用這些稽核事件來識別需要更新的特定客戶端或物件。

自訂資源定義

除了 API 伺服器能夠警告有關已棄用 API 的使用之外,從 v1.19 開始,CustomResourceDefinition 可以指示其定義的資源的特定版本已棄用。當向自訂資源的已棄用版本發出 API 請求時,將返回警告訊息,這與內建 API 的行為一致。

CustomResourceDefinition 的作者也可以根據需要自訂每個版本的警告。這允許他們在需要時提供指向遷移指南或其他資訊的指標。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
  name: crontabs.example.com
spec:
  versions:
  - name: v1alpha1
    # This indicates the v1alpha1 version of the custom resource is deprecated.
    # API requests to this version receive a warning in the server response.
    deprecated: true
    # This overrides the default warning returned to clients making v1alpha1 API requests.
    deprecationWarning: "example.com/v1alpha1 CronTab is deprecated; use example.com/v1 CronTab (see http://example.com/v1alpha1-v1)"
    ...

  - name: v1beta1
    # This indicates the v1beta1 version of the custom resource is deprecated.
    # API requests to this version receive a warning in the server response.
    # A default warning message is returned for this version.
    deprecated: true
    ...

  - name: v1
    ...

准入 Webhook

准入 webhook 是將自訂策略或驗證與 Kubernetes 集成的主要方式。從 v1.19 開始,准入 webhook 可以返回警告訊息,這些訊息將傳遞給請求 API 的客戶端。警告可以與允許或拒絕的准入響應一起返回。

作為範例,為了允許請求但警告已知無法正常運作的組態,准入 webhook 可以發送此響應。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true,
    "warnings": [
      ".spec.memory: requests >1GB do not work on Fridays"
    ]
  }
}

如果您正在實作返回警告訊息的 webhook,以下是一些提示:

  • 請勿在訊息中包含 "Warning:" 前綴(該前綴由客戶端在輸出時添加)。
  • 使用警告訊息來描述發出 API 請求的客戶端應更正或注意的問題。
  • 簡潔扼要;如果可能,請將警告限制為 120 個字元。

准入 webhook 可以透過多種方式使用這項新功能,我期待看到人們提出什麼。以下是一些幫助您入門的想法:

  • webhook 實作新增了“抱怨”模式,在該模式下,它們返回警告而不是拒絕,以允許在開始強制執行策略之前嘗試策略以驗證其是否按預期運作。
  • “lint”或“vet”風格的 webhook,檢查物件並在未遵循最佳實務時顯示警告。

自訂客戶端處理

使用 k8s.io/client-go 程式庫發出 API 請求的應用程式可以自訂如何處理從伺服器返回的警告。預設情況下,警告會在收到時記錄到 stderr,但此行為可以按進程按客戶端自訂。

此範例示範如何使您的應用程式的行為類似於 kubectl,覆蓋全進程的訊息處理以消除重複的警告,並在使用彩色輸出時突出顯示訊息。

import (
  "os"
  "k8s.io/client-go/rest"
  "k8s.io/kubectl/pkg/util/term"
  ...
)

func main() {
  rest.SetDefaultWarningHandler(
    rest.NewWarningWriter(os.Stderr, rest.WarningWriterOptions{
        // only print a given warning the first time we receive it
        Deduplicate: true,
        // highlight the output with color when the output supports it
        Color: term.AllowsColorOutput(os.Stderr),
      },
    ),
  )

  ...

下一個範例示範如何構建忽略警告的客戶端。這對於操作所有資源類型的元數據(在運行時使用探索 API 動態找到)並且不會從有關特定資源已棄用的警告中受益的客戶端很有用。對於需要使用特定 API 的客戶端,不建議抑制棄用警告。

import (
  "k8s.io/client-go/rest"
  "k8s.io/client-go/kubernetes"
)

func getClientWithoutWarnings(config *rest.Config) (kubernetes.Interface, error) {
  // copy to avoid mutating the passed-in config
  config = rest.CopyConfig(config)
  // set the warning handler for this client to ignore warnings
  config.WarningHandler = rest.NoWarnings{}
  // construct and return the client
  return kubernetes.NewForConfig(config)
}

Kubectl 嚴格模式

如果您想確保盡快注意到棄用並儘早開始解決它們,kubectl 在 v1.19 中新增了 --warnings-as-errors 選項。當使用此選項調用時,kubectl 會將從伺服器收到的任何警告視為錯誤,並以非零退出代碼退出。

kubectl applying a manifest file with a --warnings-as-errors flag, displaying a warning message and exiting with a non-zero exit code.

這可以用於 CI 作業中,以將 manifest 應用於當前伺服器,並且需要以零退出代碼通過才能使 CI 作業成功。

未來可能性

現在我們有了一種在上下文中向使用者傳達有幫助資訊的方式,我們已經在考慮其他方法,我們可以利用這種方式來改善人們使用 Kubernetes 的體驗。我們接下來關注的幾個領域是警告有關已知有問題的值,由於相容性原因,我們無法完全拒絕這些值,以及警告有關使用已棄用的欄位或欄位值(例如使用 beta os/arch 節點標籤的選擇器,在 v1.14 中已棄用)。我很高興看到該領域取得進展,繼續使 Kubernetes 更易於使用。