Kubernetes YAML最佳實踐和策略

語言: CN / TW / HK

Kubernetes工作負載最常用YAML格式的檔案來定義。

YAML的問題之一就是很難描述清單檔案之間的約束或關係。
如果你希望檢查是否已從受信任的登錄檔中提取部署到群集中的所有映像,該怎麼辦?
如何防止沒有Pod安全策略的工作負載提交到叢集?
整合靜態檢查可以在更接近開發生命週期的時間內捕獲錯誤和違反策略的行為。
並且由於改善了資源定義的有效性和安全性,因此你可以相信生產工作負載遵循最佳實踐。



Kubernetes YAML檔案的靜態檢查生態系統可以分為以下幾類:

  • API驗證程式:此類工具針對Kubernetes API伺服器驗證給定的YAML清單。
  • 內建檢查器:此類工具捆綁了針對安全性,最佳實踐等的自覺檢查。
  • 自定義驗證器:此類工具允許使用多種語言(例如python和Javascript)編寫自定義檢查。

在本文中,你將學習到六個不同的工具:

Kubeval
Kube-score
Config-lint
Copper
Conftest
Polaris

Let's Go ~~~

基準服務

首先部署一個基準服務,以便後面測試對比

apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-echo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: http-echo
  template:
    metadata:
      labels:
        app: http-echo
    spec:
      containers:
      - name: http-echo
        image: hashicorp/http-echo
        args: ["-text", "hello-world"]
        ports:
        - containerPort: 5678
---
apiVersion: v1
kind: Service
metadata:
  name: http-echo
spec:
  ports:
  - port: 5678
    protocol: TCP
    targetPort: 5678
  selector:
    app: http-echo

部署完成並驗證如下:

[root@k8s-node001 Test]# kubectl  get po
NAME                            READY   STATUS    RESTARTS   AGE
http-echo-57dd74545-rtxzm       1/1     Running   0          65s
http-echo-57dd74545-trst7       1/1     Running   0          65s

[root@k8s-node001 Test]# kubectl  get svc
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
http-echo        ClusterIP   10.102.221.64   <none>        5678/TCP         70s

[root@k8s-node001 Test]# curl  10.102.221.64:5678
hello-world

以上YAML檔案能部署成功,但是,它遵循最佳做法嗎?

Let's start.

kubeval

kubeval的前提是與Kubernetes的任何互動都通過其REST API進行。
因此,可以使用API模式來驗證給定的YAML輸入是否符合該模式。

安裝kubeval

wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz
tar xf kubeval-linux-amd64.tar.gz
cp kubeval /usr/local/bin

現在我們來改下base.yaml,刪除

  selector:
    matchLabels:
      app: http-echo

然後使用kubeval對base.yaml檢查

[root@k8s-node001 Test]# kubeval  base.yaml
WARN - base.yaml contains an invalid Deployment (http-echo) - selector: selector is required
PASS - base.yaml contains a valid Service (http-echo)

輸出看到一個WARN,提示selector是必須的欄位
然後恢復selector,再次檢查

[root@k8s-node001 Test]# kubeval  base.yaml 
PASS - base.yaml contains a valid Deployment (http-echo)
PASS - base.yaml contains a valid Service (http-echo)

檢查PASS

kubeval之類的工具的優勢在於,咱們可以在部署週期的早期發現此類錯誤。
另外,您不需要訪問叢集即可執行檢查-它們可以離線執行。
預設情況下,kubeval會根據最新的未釋出的Kubernetes API模式驗證資源。
更多用法詳情請參見官網


kube-score

kube-score會對你提供的YAML清單進行分析,並針對叢集的內建檢查對其進行評分。
kube-score提供線上版和離線
本文偷懶就用線上版了
首先開啟https://kube-score.com/ ,然後在輸入框貼入寫好的YAML清單,這裡以上文base.yaml來分析


Kubernetes YAML最佳實踐和策略
解析結果如下

Kubernetes YAML最佳實踐和策略

從如上可以看到針對這個檔案給出的建議,比如資源限制、映象TAG、Pod網路策略等。不錯吧,非常好用的工具。。。

當然,kube-score並不可擴充套件,並且您不能新增或調整策略。
如果要編寫自定義檢查以符合組織策略,則可以使用以下四個工具之一:config-lint,copper,conftest或Polaris。

Config-lint

Config-lint是用於驗證以YAML,JSON,Terraform,CSV和Kubernetes清單編寫的配置檔案的工具。

安裝Config-lint

wget https://github.com/stelligent/config-lint/releases/download/v1.6.0/config-lint_Linux_x86_64.tar.gz
tar -zxf config-lint_Linux_x86_64.tar.gz
mv config-lint /usr/local/bin/

Config-lint並沒有對Kubernetes清單進行內建檢查。你必須編寫自己的規則才能執行任何驗證。
規則被寫為YAML檔案,稱為規則集,並具有以下結構:

version: 1
description: Rules for Kubernetes spec files
type: Kubernetes
files:
  - "*.yaml"
rules:
   # list of rules

假設咱們希望檢查部署中的映象是否總是從可信任的倉庫(例如kubeops.net/app:1.0 )中提取。
實施此類檢查的config-lint規則如下所示:

- id: MY_DEPLOYMENT_IMAGE_TAG
  severity: FAILURE
  message: Deployment must use a valid image tag
  resource: Deployment
  assertions:
    - every:
        key: spec.template.spec.containers
        expressions:
          - key: image
            op: starts-with
            value: "kubeops.net/"

一個完整的規則集如下所示:

version: 1
description: Rules for Kubernetes spec files
type: Kubernetes
files:
  - "*.yaml"
rules:
  - id: DEPLOYMENT_IMAGE_REPOSITORY
    severity: FAILURE
    message: Deployment must use a valid image repository
    resource: Deployment
    assertions:
      - every:
          key: spec.template.spec.containers
          expressions:
            - key: image
              op: starts-with
              value: "kubeops.net/"

如果要測試檢查,可以將規則集另存為check_image_repo.yaml。
然後使用config-lint執行檢查

[root@k8s-node001 Test]# config-lint -rules check_image_repo.yaml base.yaml
[
  {
    "AssertionMessage": "Every expression fails: And expression fails: image does not start with kubeops.net/",
    "Category": "",
    "CreatedAt": "2020-11-02T08:28:43Z",
    "Filename": "base.yaml",
    "LineNumber": 0,
    "ResourceID": "http-echo",
    "ResourceType": "Deployment",
    "RuleID": "DEPLOYMENT_IMAGE_REPOSITORY",
    "RuleMessage": "Deployment must use a valid image repository",
    "Status": "FAILURE"
  }
]

可以看到Every expression fails,檢測不通過。
現在我們來改下images地址為image: kubeops.net/http-echo,再來檢查一次

[root@k8s-node001 Test]# config-lint -rules check_image_repo.yaml base.yaml
[]

輸出不報錯即為成功。

Config-lint是一個很有前途的框架,可以讓你使用YAML DSL為Kubernetes YAML清單編寫自定義檢查。
但是,如果您想表達更復雜的邏輯和檢查該怎麼辦?
YAML對此是否也有限制?
如果您可以使用真正的程式語言來表達這些檢查,該怎麼辦?接下來看Copper


Copper

Copper V2是一個使用自定義檢查來驗證清單的框架,就像config-lint一樣。
但是,Copper不使用YAML定義檢查。
相反,測試是用JavaScript編寫的,而Copper提供了一個包含一些基本幫助程式的庫,以幫助讀取Kubernetes物件和報告錯誤。

安裝Copper

https://github.com/cloud66-oss/copper/releases/download/2.0.1/linux_amd64_2.0.1
mv linux_amd64_2.0.1 copper
chmod + x copper
mv copper /usr/local/bin/

與config-lint相似,Copper並沒有提供內建檢查。
讓我們自定義一個檢查,以確保部署映象tag必須非latest。
check_image_repo.js

$$.forEach(function($){
    if ($.kind === 'Deployment') {
        $.spec.template.spec.containers.forEach(function(container) {
            var image = new DockerImage(container.image);
            if (image.tag === 'latest') {
                errors.add_error('no_latest',"latest is used in " + $.metadata.name, 1)
            }
        });
    }
});

執行檢查

[root@k8s-node001 Test]# copper validate --in=base.yaml --validator=check_image_tag.js
Check no_latest failed with severity 1 due to latest is used in http-echo
Validation failed

現在修改為image: kubeops.net/http-echo:v1.0.0

[root@k8s-node001 Test]# copper validate --in=base.yaml --validator=check_image_tag.js
Validation successful

更多用法參見

Conftest

Conftest是用於配置資料的測試框架,可用於檢查和驗證Kubernetes清單。
測試使用專用查詢語言Rego編寫的。

安裝Conftest

wget https://github.com/open-policy-agent/conftest/releases/download/v0.21.0/conftest_0.21.0_Linux_x86_64.tar.gz
tar -xzf conftest_0.21.0_Linux_x86_64.tar.gz
mv conftest /usr/local/bin

與config-lint和copper類似,conftest沒有任何內建檢查。

首先建立一個新目錄conftest-checks和一個名為check_image_registry.rego的檔案,其內容如下:

package main

deny[msg] {

  input.kind == "Deployment"
  image := input.spec.template.spec.containers[_].image
  not startswith(image, "kubeops.net/")
  msg := sprintf("image '%v' doesn't come from kubeops.net repository", [image])
}

先修改base.yaml,image: docker.io/http-echo
使用conftest執行檢測

[root@k8s-node001 Test]# conftest test --policy ./conftest-checks base.yaml 
FAIL - base.yaml - image 'docker.io/http-echo:v1.0.0' doesn't come from kubeops.net repository

2 tests, 1 passed, 0 warnings, 1 failure, 0 exceptions

再次修改為base.yaml,image: kubeops.net/http-echo

[root@k8s-node001 Test]# conftest test --policy ./conftest-checks base.yaml 

2 tests, 2 passed, 0 warnings, 0 failures, 0 exceptions

更多用法參見

Polaris

最後一個工具了,Polaris既可以安裝在叢集內部,也可以作為命令列工具來靜態分析Kubernetes清單。
作為命令列工具執行時,它包含多個內建檢查,涉及諸如安全性和最佳實踐等方面,類似於kube-score。
另外,你可以使用它來編寫類似於config-lint,copper和conftest的自定義檢查。
換句話說,Polaris結合了兩類的優點:內建和自定義檢查器。


安裝Polaris,這裡只安裝命令列模式

wget https://github.com/FairwindsOps/polaris/releases/download/1.2.1/polaris_1.2.1_linux_amd64.tar.gz
tar -zxf polaris_1.2.1_linux_amd64.tar.gz
 mv polaris /usr/local/bin/

安裝完成後,就可以使用Polaris對base.yaml進行檢查
[root@k8s-node001 Test]# polaris audit --audit-path base.yaml
結果如下,資訊比較多,這裡只擷取部分資訊,自己可以仔細看看分析出來的結果。

  "PolarisOutputVersion": "1.0",
  "AuditTime": "0001-01-01T00:00:00Z",
  "SourceType": "Path",
  "SourceName": "base.yaml",
  "DisplayName": "base.yaml",
  "ClusterInfo": {
    "Version": "unknown",
    "Nodes": 0,
    "Pods": 1,
    "Namespaces": 0,
    "Controllers": 1
  },
  "Results": [
    {
      "Name": "http-echo",
      "Namespace": "",
      "Kind": "Deployment",
      "Results": {},
      "PodResult": {
        "Name": "",
        "Results": {
          "hostIPCSet": {
            "ID": "hostIPCSet",
            "Message": "Host IPC is not configured",
            "Success": true,
            "Severity": "danger",
            "Category": "Security"
..............
              "tagNotSpecified": {
                "ID": "tagNotSpecified",
                "Message": "Image tag is specified",
                "Success": true,
                "Severity": "danger",
                "Category": "Images"
              }
            }
          }
        ]
      },
      "CreatedTime": "0001-01-01T00:00:00Z"
    }
  ]
}

另外,可以只輸出評分

[root@k8s-node001 Test]# polaris audit --audit-path base.yaml --format score
66

下面使用YAML程式碼段定義了一個稱為checkImageRepo的新檢查:
config_with_custom_check.yaml

checks:
  checkImageRepo: danger

customChecks:
  checkImageRepo:
    successMessage: Image registry is valid
    failureMessage: Image registry is not valid
    category: Images
    target: Container
    schema:
      '$schema': http://json-schema.org/draft-07/schema
      type: object
      properties:
        image:
          type: string
          pattern: ^kubeops.net/.+$

現在base.yaml的image為:image: docker.io/http-echo:v1.0.0
我們來使用自定義的規則執行檢查

[root@k8s-node001 Test]# polaris audit --config config_with_custom_check.yaml --audit-path base.yaml
{
  "PolarisOutputVersion": "1.0",
  "AuditTime": "0001-01-01T00:00:00Z",
  "SourceType": "Path",
  "SourceName": "base.yaml",
  "DisplayName": "base.yaml",
  "ClusterInfo": {
    "Version": "unknown",
    "Nodes": 0,
    "Pods": 1,
    "Namespaces": 0,
    "Controllers": 1
  },
  "Results": [
    {
      "Name": "http-echo",
      "Namespace": "",
      "Kind": "Deployment",
      "Results": {},
      "PodResult": {
        "Name": "",
        "Results": {},
        "ContainerResults": [
          {
            "Name": "http-echo",
            "Results": {
              "checkImageRepo": {
                "ID": "checkImageRepo",
                "Message": "Image registry is not valid",
                "Success": false,
                "Severity": "danger",
                "Category": "Images"
              }
            }
          }
        ]
      },
      "CreatedTime": "0001-01-01T00:00:00Z"
    }
  ]
}

結果顯示"Message": "Image registry is not valid", "Success": false,
然後修改base.yaml的image為:image: kubeops.net/http-echo:v1.0.0
再次執行檢查

[root@k8s-node001 Test]# polaris audit --config config_with_custom_check.yaml --audit-path base.yaml 
{
  "PolarisOutputVersion": "1.0",
  "AuditTime": "0001-01-01T00:00:00Z",
  "SourceType": "Path",
  "SourceName": "base.yaml",
  "DisplayName": "base.yaml",
  "ClusterInfo": {
    "Version": "unknown",
    "Nodes": 0,
    "Pods": 1,
    "Namespaces": 0,
    "Controllers": 1
  },
  "Results": [
    {
      "Name": "http-echo",
      "Namespace": "",
      "Kind": "Deployment",
      "Results": {},
      "PodResult": {
        "Name": "",
        "Results": {},
        "ContainerResults": [
          {
            "Name": "http-echo",
            "Results": {
              "checkImageRepo": {
                "ID": "checkImageRepo",
                "Message": "Image registry is valid",
                "Success": true,
                "Severity": "danger",
                "Category": "Images"
              }
            }
          }
        ]
      },
      "CreatedTime": "0001-01-01T00:00:00Z"
    }
  ]
}

從輸出看到 "Message": "Image registry is valid","Success": true,,檢查通過。。。
更多用法參見

總結

儘管有很多工具可以對Kubernetes YAML檔案進行驗證,評分和整理,但重要的是要有一個健康的模型來設計和執行檢查。
例如,如果你要考慮通過管道的Kubernetes清單,則kubeval可能是該管道中的第一步,因為它可以驗證物件定義是否符合Kubernetes API模式。一旦此檢查成功,你可以繼續進行更詳盡的測試,例如標準最佳實踐和自定義策略。
Kube-score和Polaris是比較好的選擇。
如果你有複雜的要求,並且想要自定義檢查的細節,則應考慮使用copper ,config-lint和conftest。
儘管conftest和config-lint都使用更多的YAML來定義自定義驗證規則,但是Copper允許訪問一種真正的程式語言,這使其頗具吸引力。
但是,你應該使用其中之一併從頭開始編寫所有檢查嗎?還是應該使用Polaris並僅編寫其他自定義檢查?
這都取決於你自己,合適自己的才是最好的。。。