警告:前方有用的警告

作為 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 的版本才意識到他們依賴於廢棄的 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。當用戶應用他們未建立的清單時,這尤其有用,這樣他們就有時間聯絡作者以請求更新版本。

我們還意識到,使用廢棄 API 的人通常與負責升級叢集的人不是同一個人,因此我們添加了兩個面向管理員的工具,以幫助跟蹤廢棄 API 的使用並確定何時可以安全升級。

指標

從 Kubernetes v1.19 開始,當向廢棄的 REST API 端點發出請求時,`kube-apiserver` 程序中的 `apiserver_requested_deprecated_apis` 儀表指標設定為 `1`。此指標具有 API `group`、`version`、`resource` 和 `subresource` 的標籤,以及一個 `removed_release` 標籤,該標籤指示 API 將不再提供的 Kubernetes 版本。

這是一個使用 `kubectl`、prom2jsonjq 查詢的示例,用於確定已從 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"`。管理員可以使用這些審計事件來識別需要更新的特定客戶端或物件。

自定義資源定義(Custom Resource Definitions)

除了 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 任務中用於將清單應用到當前伺服器,並且要求以零退出程式碼透過才能使 CI 任務成功。

未來可能性

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