Kubenetes-扩展教程-一-

51 阅读23分钟

Kubenetes 扩展教程(一)

原文:Extending Kubernetes

协议:CC BY-NC-SA 4.0

一、简介

生活就像洋葱;你一层一层地剥开它,有时你会流泪。

卡尔沙堡

美国诗人、传记作家、记者和编辑,三次获得普利策奖

Kubernetes 就像洋葱。你一次剥开一层,有时你会流泪,检查你的 YAML 文件,并阅读更多的文档。

Kubernetes 是一个复杂的系统。第一章将从 Kubernetes 的简史以及它如何发展成为一个复杂的系统开始。虽然它已经有许多层,但它还可以用附加层来扩展。本章还将讨论如何配置 Kubernetes 系统、它的扩展模式和要点。在本章的最后,你将理解 Kubernetes 的复杂性和它的能力。

让我们先简要回顾一下 Kubernetes 的历史及其特点。

忽必烈再世

Kubernetes 是一个管理容器化应用的开源系统。名字源于希腊语,意为舵手。所以,说 Kubernetes 是帮助你在容器和微服务的风暴海洋中找到莫比迪克的工具并没有错。

谷歌在 2014 年开源了 Kubernetes,它是几十年来在容器中运行生产工作负载的经验积累。2015 年,谷歌宣布将 Kubernetes 项目移交给云计算原生计算基金会(CNCF)。 1 CNCF 拥有超过 500 名成员, 2 包括全球最庞大的公有云和企业软件公司以及超过 100 家创新创业公司。该基金会是许多发展最快的项目的供应商中立之家,包括 Kubernetes普罗米修斯特使

Kubernetes 是目前最受欢迎的开源项目之一,有近 3000 名贡献者,超过 1000 个拉请求和 2000 个开放问题。这个库(图 1-1 )可以在 GitHub 上以名称kubernetes/kubernetes获得。 3

img/503015_1_En_1_Fig1_HTML.jpg

图 1-1

库存储库

有大量的开放问题需要解决,如果你想深入开源世界做出贡献,这个社区也是最受欢迎的。现在让我们再深入一个层次,看看我们所说的 Kubernetes 系统是什么意思。

Kubernetes 被设计为在集群上运行。Kubernetes 集群由节点组成,容器化的应用在这些节点上运行。

我们可以从逻辑上将 Kubernetes 系统分成两部分:控制平面和工作节点。控制平面管理工作节点和群集的工作负载,而工作节点运行工作负载。在图 1-2 中,您可以看到 Kubernetes 集群的组件是如何联系在一起的。

img/503015_1_En_1_Fig2_HTML.jpg

图 1-2

库比特组件

控制平面组件

控制平面是 Kubernetes 集群的大脑,负责做出决策、检测事件,并在需要时做出响应。例如,期望控制平面将 pod 的调度决策给予工作节点,识别故障节点,并重新调度新的 pod 以确保可扩展性。

控制平面组件可以在 Kubernetes 集群中的任何节点上运行;但是,为控制平面组件保存一些节点是一种典型的方法。这种方法将工作负载从集群中的控制平面组件中分离出来,使操作节点进行扩展和缩减或维护变得更加容易。

现在,让我们回顾一下每个控制平面组件及其对群集的重要性。

多维数据集 apiserver

Kubernetes API 是控制平面的前端,kube-apiserver公开了它。kube-apiserver可以通过运行多个实例进行水平扩展,从而创建一个高度可用的 Kubernetes API。

和 cd

etcd是一个开源的分布式键值存储,Kubernetes 将其所有数据存储在其中。集群的状态和变化仅通过kube-apiserver保存在etcd中,并且可以水平扩展etcd

多维数据集调度程序

Kubernetes 是一个容器编排系统,它需要将容器化的应用分配给节点。kube-scheduler负责通过考虑资源需求、可用资源、硬件和策略约束、相似性规则和数据局部性来做出调度决策。

kube-控制器-管理器

Kubernetes 的关键设计概念之一是控制器。Kubernetes 中的控制器是控制循环,用于监视集群的状态,并在需要时做出更改。每个控制器都与 Kubernetes API 交互,并试图将当前的集群状态转移到所需的状态。在本书中,您不仅会熟悉原生的 Kubernetes 控制器,还会学习如何创建新的控制器来实现新的功能。kube-controller-manager是 Kubernetes 集群的一组核心控制器。

云控制器管理器

Kubernetes 被设计成一个独立于平台的可移植系统。因此,it 需要与云提供商进行交互,以创建和管理节点、路由或负载平衡器等基础设施。cloud-controller-manager是运行特定于云提供商的控制器的组件。

节点组件

节点组件安装在 Kubernetes 集群中的每个工作节点上。工作节点负责运行容器化的应用。在 Kubernetes 中,容器被分组到一个名为 pod 的资源中。kube-scheduler将 pod 分配给节点,节点组件确保它们启动并运行。

忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈

kubelet是运行在各个节点上的代理。它获取分配给该节点的 pod 的规范。然后,它与容器运行时交互,在 pod 中创建、删除和观察容器的状态。

多维数据集代理

容器化的应用在 Kubernetes 集群中运行时,就像在单个网络中运行一样。作为网络代理在每个节点上运行,并连接应用。它还维护群集内外网络通信的网络规则。

在生产环境中,控制平面组件在多个节点上运行,以提供容错和高可用性。类似地,工作节点的数量随着工作负载和资源需求而扩展。另一方面,可以创建更多可移植的 Kubernetes 系统,在 Docker 容器或虚拟机内的单个节点上运行,用于开发和测试环境。在本书中,我们将创建生产就绪型和单节点 Kubernetes 集群,并观察它们的运行情况。在接下来的小节中,我们将重点介绍如何配置 Kubernetes 系统,以了解它的功能。

配置 Kubernetes 集群

您可以通过两种广泛的方法来约束或释放 Kubernetes 集群:配置和扩展。在配置方法中,您可以更改标志、配置文件或 API 资源。这一节将着重于配置 Kubernetes,然后我们将在本书的其余部分把重点转移到扩展上。

参考文档中定义了控制平面和节点组件的标志和配置文件。 4 还有,也有可能弄脏自己的手,用 Docker 图像检查一下。让我们从kube-apiserver开始,检查它的标志,如清单 1-1 所示。

$ docker run -it --rm k8s.gcr.io/kube-apiserver:v1.19.0 kube-apiserver --help

The Kubernetes API server validates and configures data for the api objects which include pods, services, replicationcontrollers, and others. The API Server services REST operations and provides the frontend to the cluster's shared state through which all other components interact.

Usage:
  kube-apiserver [flags]

Generic flags:

      --advertise-address       ip
      The IP address on which to advertise the apiserver to members of the cluster. ...
      ...
      --cors-allowed-origins    strings
      List of allowed origins for CORS, comma separated.
      ...

Listing 1-1kube-apiserver flags

命令行输出是巨大的,kube-apiserver二进制文件有将近 150 个标志。但是,每个集群管理员都需要知道一个标志:--feature-gates。特性门是一组键和值对,用于启用 alpha 或实验性 Kubernetes 特性。它在每个 Kubernetes 组件中都可用,并且可以通过它的帮助来访问。这次让我们检查一下kube-scheduler,如清单 1-2 所示。

$ docker run -it --rm k8s.gcr.io/kube-scheduler:v1.19.0 kube-scheduler --help 2>&1 |grep -A 250 feature-gates

--feature-gates     mapStringBool
A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:
         APIListChunking=true|false (BETA - default=true)       APIPriorityAndFairness=true|false (ALPHA - default=false)      APIResponseCompression=true|false (BETA - default=true)       AllAlpha=true|false (ALPHA - default=false)
         AllBeta=true|false (BETA - default=false)
         ...

Listing 1-2kube-scheduler flags

特定版本的kube-scheduler有 85 个特征门选项,因此输出也很长。Kubernetes 中的实验特性需要在毕业或贬值前进行 alpha 和 beta 测试。您可以在官方参考文档 5 中跟踪特性的状态及其默认值、阶段、开始和结束版本,如图 1-3 所示。

img/503015_1_En_1_Fig3_HTML.jpg

图 1-3

特征门

在受管理的 Kubernetes 系统中,如亚马逊弹性 Kubernetes 服务(EKS)或谷歌 Kubernetes 引擎(GKE) ,无法编辑控制平面组件的标志。但是,在 Google Kubernetes 引擎 6 中有选项启用所有 alpha 功能,带有类似于--feature-gates=AllAlpha=true--enable-kubernetes-alpha标志。使用 alpha 集群对新特性进行早期测试和验证是很有价值的。

Kubernetes 的配置支持设计定制的集群。因此,掌握控制平面和节点组件的配置参数至关重要。然而,配置参数只允许您调整 Kubernetes 中已经存在的内容。在下一节中,我们将通过扩展来扩展 Kubernetes 的边界。

Kubernetes 扩展模式

Kubernetes 设计以 Kubernetes API 为核心。所有的 Kubernetes 组件如kube-scheduler和客户端如kubectl都与 Kubernetes API 交互操作。同样,扩展模式被设计成与 API 交互。然而,与客户机或 Kubernetes 组件不同,扩展模式丰富了 Kubernetes 的功能。有三种广为接受的设计模式来扩展 Kubernetes。

控制器

控制器是用于管理至少一种 Kubernetes 资源类型的循环。

他们检查资源的specstatus字段,并在需要时采取行动。在spec字段中,定义了期望状态,而status字段代表实际状态。我们可以用图 1-4 来说明控制器的流程。

img/503015_1_En_1_Fig4_HTML.jpg

图 1-4

Kubernetes 中的控制器模式

让我们从 Kubernetes 拿一个真正的控制器,试着理解它们是如何操作的。CronJob是一个 Kubernetes 资源,支持按照重复的时间表运行JobsJob是另一个 Kubernetes 资源,它运行一个或多个 pod 并确保它们成功终止。CronJob在 Go 包k8s.io/kubernetes/pkg/controller/cronjob中定义了一个控制器。您可以像下面这样创建一个example CronJob资源。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: example
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes CronJob
          restartPolicy: OnFailure

Listing 1-3Example CronJob resource

期望的状态在spec字段中,有两个重要的部分:schedulejobTemplateschedule定义间隔,example CronJob以分钟为单位。jobTemplate字段具有要执行的容器的Job定义。

我们可以期望CronJob控制器监视CronJob资源,并在它们的调度发生时创建Jobs。源代码比较长,但是我们可以突出一些重点。cronjob_controller.go中的syncOne函数负责创建Jobs并更新单个CronJob实例的状态。

jobReq, err := getJobFromTemplate(cj, scheduledTime)
...
jobResp, err := jc.CreateJob(cj.Namespace, jobReq)
...
klog.V(4).Infof("Created Job %s for %s", jobResp.Name, nameForLog)
recorder.Eventf(cj, v1.EventTypeNormal, "SuccessfulCreate", "Created job %v", jobResp.Name)

...

// Add the just-started job to the status list.
ref, err := getRef(jobResp)
if err != nil {
      klog.V(2).Infof("Unable to make object reference for job for %s", nameForLog)
} else {
      cj.Status.Active = append(cj.Status.Active, *ref)
}
cj.Status.LastScheduleTime = &metav1.Time{Time: scheduledTime}
if _, err := cjc.UpdateStatus(cj); err != nil {
      klog.Infof("Unable to update status for %s (rv = %s): %v", nameForLog, cj.ResourceVersion, err)
}
...

Listing 1-4CronJob controller

当您部署示例CronJob资源时,您可以在集群中看到更新的状态和创建的作业资源,如清单 1-5 所示。

$ kubectl apply -f cronjob_example.yaml
cronjob.batch/example created

$ kubectl get cronjob example -o yaml

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  ...
  name: example
  namespace: default
  ...
spec:
        concurrencyPolicy: Allow
        failedJobsHistoryLimit: 1
        jobTemplate:
            ...
  schedule: '*/1 * * * *'
  successfulJobsHistoryLimit: 3
  suspend: false
status:
  active:
  - apiVersion: batch/v1
    kind: Job
    name: example-1598968200
    namespace: default
    resourceVersion: "588"
    uid: e4603eb1-e2b3-419f-9d35-eeea9021fc34
  lastScheduleTime: "2020-09-01T13:50:00Z"

$ kubectl get jobs
NAME                 COMPLETIONS   DURATION   AGE
example-1598968200   1/1           4s         119s
example-1598968260   1/1           4s         59s
example-1598968320   1/1           3s         8s

Listing 1-5CronJob in action

Note

CronJob 控制器的源代码可以在 GitHub 上找到: https://github.com/kubernetes/kubernetes/tree/master/pkg/controller/cronjob

借助 Kubernetes 中的定制资源,控制器提供了一个健壮的扩展模式。可以通过定义定制资源来扩展 Kubernetes API,并通过控制器来管理它们。在第四章中,我们将扩展 Kubernetes API 并编写定制控制器来实现这种设计模式。

web 手册

Webhook 是一个 HTTP 回调函数,用于发送事件通知并获取结果。在 Kubernetes API 中,可以通过外部 webhooks 来验证一些事件,如授权、验证或资源突变。Kubernetes 查询外部 REST 服务来处理此类事件。我们可以用图 1-5 来说明请求的流程。

img/503015_1_En_1_Fig5_HTML.jpg

图 1-5

Kubernetes 中的请求流

当一个新用户想要连接到 Kubernetes API 时,请求被打包并发送到定义的 webhook 地址,并检查响应。如果用户被授权,webhook 服务器可以发送如下数据,如清单 1-6 所示。

{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "status": {
    "allowed": true
  }
}

Listing 1-6Authorization webhook response

类似地,如果用户想要更改 Kubernetes API 中的资源,可以查询 webhook 服务器来验证更改。当 webhook 后端通过发送类似于清单 1-7 的以下数据接受更改时,Kubernetes API 将应用这些更改。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true
  }
}

Listing 1-7Change webhook response

Webhook 后端易于遵循设计模式来扩展软件应用。然而,webhooks 给系统增加了一个故障点,在开发和运行过程中需要非常注意。

二进制插件

在二进制插件模式中,Kubernetes 组件执行第三方二进制文件。像kubelet这样的节点组件或者像kubectl这样的客户端程序利用这种模式,因为它需要主机系统上额外的二进制文件,例如,kubectl用清单 1-8 中的函数执行第三方二进制文件。

// Execute implements PluginHandler
func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {

      // Windows does not support exec syscall.
      if runtime.GOOS == "windows" {
            cmd := exec.Command(executablePath, cmdArgs...)
            cmd.Stdout = os.Stdout
            cmd.Stderr = os.Stderr
            cmd.Stdin = os.Stdin
            cmd.Env = environment
            err := cmd.Run()
            if err == nil {
                  os.Exit(0)
            }
            return err
      }

      // invoke cmd binary relaying the environment and args given
      ..
      return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment)
}

Listing 1-8kubectl binary plugin handling

Go 函数Execute调用外部二进制文件,并将其输入和输出捕获到命令行。在接下来的章节中,您将创建类似的插件,并看到二进制插件模式的运行。

Note

kubectl的源代码可以在 GitHub: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cmd.go 获得。

作为软件工程设计模式,扩展模式在 Kubernetes 中被接受,并且是常见问题的可重复解决方案。如果您有类似的障碍,这些模式有助于实现解决方案。但是,应该记住,设计模式和扩展模式都不是灵丹妙药。它们应该被视为扩展 Kubernetes 系统的方法。有了多个组件和 API 端点,Kubernetes 对扩展有了广泛的开放点。在下一节中,我们将对 Kubernetes 中的这些扩展点进行更技术性的概述。

立方扩展点

Kubernetes 是一个开放的系统,但它并不像 Kubernetes 的每个组件都是一个乐高积木,可以插入新的东西。有一些特殊的扩展点,您可以扩展 Kubernetes 系统的技能。有五组主要的扩展点及其实现模式和工作区域:

  • kubectl 插件 : kubectl是用户与 Kubernetes API 交互不可或缺的工具。可以通过在 CLI 中添加新命令来扩展kubectlkubectl插件实现了二进制插件扩展模式,用户需要将它们安装在本地工作区。

  • API 流扩展:对 Kubernetes API 的每个请求都要经过几个步骤:认证、授权和准入控制。Kubernetes 用 webhooks 为每个步骤提供了一个扩展点。

  • Kubernetes API 扩展 : Kubernetes API 有各种原生资源,比如 pods 或 nodes。您可以将自定义资源添加到 API 中,并对其进行扩展以适用于您的新资源。此外,Kubernetes 为其本地资源提供了控制器,您可以为您的定制资源编写和运行控制器。

  • 调度器扩展 : Kubernetes 有一个控制平面组件,即kube-scheduler,用于在集群节点上分配工作负载。此外,还可以开发定制的调度程序,并在kube-scheduler旁边运行。大多数调度程序遵循控制器扩展模式来监视资源并采取行动。

  • 基础设施扩展:节点组件与基础设施交互,以创建集群网络或将卷挂载到容器。Kubernetes 通过指定的容器网络接口(CNI)和容器存储接口(CSI)拥有网络和存储的扩展点。基础设施中的扩展点遵循二进制插件扩展模式,并且需要在节点上安装可执行文件。

我们已经根据功能和实现的扩展模式对扩展点进行了分组。在本书接下来的章节中,我们将深入讨论每一组。您不仅将学习扩展点及其技术背景,还将创建它们并在集群中运行。

关键要点

  • Kubernetes 是一个复杂的系统。

  • 您可以从逻辑上将 Kubernetes 集群分成两部分:控制平面和节点组件。

  • Kubernetes 组件有丰富的配置选项。

  • 扩展 Kubernetes 有三种扩展模式:控制器、webhook 和二进制插件。

  • Kubernetes 组件及其设计允许许多开放点进行扩展:kubectl、API 流、Kubernetes API、调度器和基础设施。

在下一章中,我们将从第一个扩展点开始:kubectl插件。我们将为kubectl创建新的插件,并使用自定义命令来丰富它的功能。

Footnotes 1

www.cncf.io

  2

www.cncf.io/about/members

  3

https://github.com/kubernetes/kubernetes

  4

https://kubernetes.io/docs/reference/command-line-tools-reference/

  5

https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/

  6

https://cloud.google.com/kubernetes-engine/docs/how-to/creating-an-alpha-cluster

 

二、kubectl插件

我们塑造工具,然后工具塑造我们。

—马歇尔·麦克卢汉

媒体学者和评论家

命令行工具是开发人员的瑞士军刀。您可以连接到后端系统,运行复杂的命令,并使用它们自动化您的日常任务。Kubernetes 的官方命令行工具是kubectl。作为神话中的盖茨镇之神,kubectl是进入星团的入口之神。它允许您通过与 Kubernetes API 通信来创建工作负载、管理资源和检查状态。在这一章中,我们将通过编写插件来扩展kubectl。在本章的最后,你将开发并安装新的插件到kubectl中,并运行自定义命令。

让我们从将 Kubernetes API 网关安装到您的本地工作站开始。

kubectl 安装和使用

kubectl是与 Kubernetes API 通信的客户端工具。因此,最好有一个与 Kubernetes API 版本完全相同或接近的版本。否则,可能会有不兼容的 API 请求和失败的操作。kubectl的源代码是官方 Kubernetes 库的一部分,它的发布版本与 Kubernetes 发布版本共同管理。但是需要查看图 2-1 中的kubernetes/kubectl 1 库,了解kubectl的相关问题。

img/503015_1_En_2_Fig1_HTML.jpg

图 2-1

忽必烈〔??〕资料档案库

安装kubectl相当简单,因为它是一个单二进制应用。您需要首先从您的操作系统的发布库下载二进制文件,如清单 2-1 所示。

# Linux
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.19.0/bin/linux/amd64/kubectl

# macOS
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.19.0/bin/darwin/amd64/kubectl

Listing 2-1Downloading kubectl binary

然后您需要使二进制文件可执行。

chmod +x ./kubectl

Listing 2-2Executable kubectl binary

最后,您需要将二进制文件移动到您的PATH中。

sudo mv ./kubectl /usr/local/bin/kubectl

Listing 2-3Moving kubectl binary

您可以使用清单 2-4 中的以下命令来测试kubectl

kubectl version --client
Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.0", GitCommit:"e19964183377d0ec2052d1f1fa930c4d7575bd50", GitTreeState:"clean", BuildDate:"2020-08-26T14:30:33Z", GoVersion:"go1.15", Compiler:"gc", Platform:"darwin/amd64"}

Listing 2-4kubectl version check

该命令打印客户端版本的kubectl,即v1.19.0。在下面的练习中,您将创建一个本地 Kubernetes 集群,并继续使用更复杂的kubectl命令与集群进行交互。

EXERCISE: STARTING A LOCAL KUBERNETES CLUSTER

虽然 Kubernetes 是一个用于大型云的容器管理系统,但是也可以在本地创建单实例 Kubernetes 集群。minikube是推荐的、官方支持的创建单节点集群的方式。它主要用于开发和测试目的。

在本练习中,您将安装minikube并启动一个新的 Kubernetes 集群。

  1. 根据您的操作系统下载 minikube 的二进制文件:

    # Linux
    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
    
    # macOS
    curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64
    
    
  2. 将二进制文件安装到路径:

    # Linux
    sudo install minikube-linux-amd64 /usr/local/bin/minikube
    
    # macOS
    sudo install minikube-darwin-amd64 /usr/local/bin/minikube
    
    
  3. Start a local cluster with minikube:

    img/503015_1_En_2_Figa_HTML.png

Kubernetes 操作的简单性被打包成一个简单的命令minikube start。它下载映像,启动控制面板组件,启用插件,并验证集群组件。在最后一步中,它配置kubectl连接到由minikube创建的集群。

您有一个 Kubernetes 集群和一个客户端工具。现在,是时候通过部署应用、扩展它们并检查它们的状态来享受 Kubernetes 的乐趣了。

kubectl的用法基于清单 2-5 中的以下语法。

kubectl [command] [TYPE] [NAME] [flags]

Listing 2-5kubectl syntax

command指定要对 Kubernetes API 执行的操作,比如创建、获取、描述或删除。您可以通过运行kubectl --help列出所有命令。它列出了按功能和细节分组的所有命令,如清单 2-6 所示。

$ kubectl --help
kubectl controls the Kubernetes cluster manager.

 Find more information at: https://kubernetes.io/docs/reference/kubectl/overview/

Basic Commands (Beginner):
  create        Create a resource from a file or from stdin.
  expose        Take a replication controller, service,
                deployment or pod and expose it as a new Kubernetes Service
  run           Run a particular image on the cluster
  set           Set specific features on objects

Basic Commands (Intermediate):
  explain       Documentation of resources
  get           Display one or many resources
  edit          Edit a resource on the server
  delete        Delete resources by filenames, stdin,
                resources and names, or by resources and label
                selector
...

Listing 2-6kubectl help output

TYPE指定 Kubernetes API 资源的类型,如 pod、部署或节点。在清单 2-7 中使用以下命令可以列出 Kubernetes API 上支持的 API 资源。

$ kubectl api-resources --output=name
bindings
componentstatuses
configmaps
endpoints
events
limitranges
namespaces
nodes
persistentvolumeclaims
persistentvolumes
pods
podtemplates
replicationcontrollers

Listing 2-7kubectl API resources

这是一个很长的列表,目前在 Kubernetes API 中支持 50 多种资源。

NAME指定对其执行命令的资源的名称,操作。如果您没有指定一个NAME,那么将对该类型中的所有资源执行命令。

flags是命令的可选变量,如--namespace--kubeconfig。你可以用kubectl options列出可以传递给任何命令的选项,如清单 2-8 所示。

$ kubectl options
The following options can be passed to any command:
...
--cluster='': The name of the kubeconfig cluster to use
--context='': The name of the kubeconfig context to use
...
--kubeconfig='': Path to the kubeconfig file to use for CLI requests.
-n, --namespace='': If present, the namespace scope for this CLI request
...
--token='': Bearer token for authentication to the API server
...
-v, --v=0: number for the log level verbosity

Listing 2-8kubectl options output

您可以运行kubectl <command> --help来获得更多的信息,比如关于给定命令的选项、用法和示例。考虑到大量的资源和命令,kubectl是一个打包了大量动作的工具。建议通过尝试不同的命令来使用kubectlkubectl几乎是集群部署、状态跟踪和故障排除的唯一入口。在下面的练习中,在开发扩展之前,您将使用最常见的kubectl命令来习惯它。

EXERCISE: GETTING STARTED WITH kubectl

在本练习中,您将使用kubectl与 Kubernetes 集群进行交互。

  1. Start with checking the version of your client tool and the API server:

    $ kubectl version
    Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.0", GitCommit:"e19964183377d0ec2052d1f1fa930c4d7575bd50", GitTreeState:"clean", BuildDate:"2020-08-26T14:30:33Z", GoVersion:"go1.15", Compiler:"gc", Platform:"darwin/amd64"}
    Server Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.0", GitCommit:"e19964183377d0ec2052d1f1fa930c4d7575bd50", GitTreeState:"clean", BuildDate:"2020-08-26T14:23:04Z", GoVersion:"go1.15", Compiler:"gc", Platform:"linux/amd64"}
    
    

    它显示客户端和服务器的版本都是 1.19.0。

  2. Check the nodes available in the cluster:

    $ kubectl get nodes
    NAME     STATUS ROLES  AGE VERSION
    minikube Ready  master 88s v1.19.0
    
    

    节点也是 Kubernetes 中的一种资源类型,命令是从 Kubernetes API 中检索它们。您将有一个节点,因为您正在运行一个minikube集群。

  3. Create a deployment with the following command:

    $ kubectl create deployment my-first-deployment ​--image=nginx
    deployment.apps/my-first-deployment created
    
    

    该命令使用映像nginx创建名为my-first-deployment的部署资源类型。

  4. Check the status of the deployment created in Step 3:

    $ kubectl get deployment my-first-deployment
    NAME                READY  UP-TO-DATE  AVAILABLE AGE
    my-first-deployment  1/1   1           1         16s
    
    

    此命令检索资源及其名称。该部署有一个可用的就绪实例。

  5. Scale the deployment to five instances:

    $ kubectl scale deployment/my-first-deployment ​--replicas=5
    deployment.apps/my-first-deployment scaled
    
    

    这是一个特殊的命令,用于扩展所提供资源的实例数量。--replicas标志指定了请求的复制计数。

  6. Check the pods after scale-up:

    $ kubectl get pods
    NAME                            READY   STATUS    RESTARTS   AGE
    my-first-deployment-...-26xpn   1/1     Running   0          13s
    my-first-deployment-...-87fcw   1/1     Running   0          13s
    my-first-deployment-...-b7nzv   1/1     Running   0          2m45s
    my-first-deployment-...-kxg2w   1/1     Running   0          13s
    my-first-deployment-...-wmg92   1/1     Running   0          13s
    
    

    正如所料,现在有五个 pod,后四个是在第一个之后创建的。

  7. 使用以下命令清理部署:

    $ kubectl delete deployment my-first-deployment
    deployment.apps "my-first-deployment" deleted
    
    

您的 CLI 环境有了一个新成员,您已经开始发现它的功能。现在,是时候更进一步,扩展它的技能了。在接下来的部分,我们将继续插件设计,为kubectl添加自定义命令。

立方插件设计

核心命令对于与 Kubernetes API 的交互来说是必不可少的。插件用新的子命令扩展了kubectl,用于新的定制特性。kubectl扩展实现了二进制插件方法。与二进制插件模式一样,kubectl将第三方应用作为扩展来执行。插件二进制文件有三个主要规则:

  • -可执行

  • -用户PATH的任何地方

  • -从kubectl-开始

这三条规则基于kubectl如何发现插件。我们来看看kubectl中插件处理的源代码。

// Lookup implements PluginHandler
func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) {
      for _, prefix := range h.ValidPrefixes {
            path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename))
            if err != nil || len(path) == 0 {
      continue
      }
      return path, true
            }
            return "", false
      }

Listing 2-9Plugin handler in kubectl

注意默认插件句柄的源代码可以在 https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cmd.go 获得。

DefaultPluginHandler检查从ValidPrefixkubectl开始的路径中的可执行文件。因此,PATH环境变量中任何名为kubectl-my-first-pluginkubectl-whoami的二进制文件都是合适的kubectl插件。插件名被解释为子命令,比如名为kubectl-whoami的二进制文件代表kubectl whoami命令。因此,kubectl会检查本机实现中是否有命令,然后检查插件,如图 2-2 所示。

img/503015_1_En_2_Fig2_HTML.jpg

图 2-2

kubectl命令处理

我们来看看kubectl中的插件是如何执行的。

// Execute implements PluginHandler
func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {

    // Windows does not support exec syscall.
    if runtime.GOOS == "windows" {
         cmd := exec.Command(executablePath, cmdArgs...)
         cmd.Stdout = os.Stdout
         cmd.Stderr = os.Stderr
         cmd.Stdin = os.Stdin
         cmd.Env = environment
         err := cmd.Run()
         if err == nil {
              os.Exit(0)
         }
         return err
    }

  // invoke cmd binary relaying the environment and args given
    ..
    return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment)
}

Listing 2-10kubectl binary plugin handling

注意默认插件句柄的源代码可以在 https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/cmd.go 获得。

DefaultPluginHandler有一个Execute函数,输入可执行路径、参数和环境变量。该函数将这些变量传递给第三方二进制文件,也就是插件。在 Windows 中,它将标准输入和输出连接到命令,然后执行它。在 Linux 和 macOS 中,该函数在操作系统级别使用syscall,带有参数和环境变量。

现在,是时候通过创建插件向kubectl添加一个新的定制命令了。

创建您的第一个 kubectl 插件

您可以使用kubectl plugin 命令在本地列出可用的插件。

$ kubectl plugin list

error: unable to find any kubectl plugins in your PATH

Listing 2-11Installed

plugins

kubectl没有在本地找到插件。现在,用以下内容创建一个名为kubectl-whoami的文件。

#!/bin/bash

kubectl config view --template='{{ range .contexts }}{{ if eq .name "'$(kubectl config current-context)'" }}User: {{ printf "%s\n" .context.user }}{{ end }}{{ end }}'

Listing 2-12Plugin code

将文件移动到 PATH 环境变量中的一个文件夹,并使其可执行。

sudo chmod +x ./kubectl-whoami
sudo mv ./kubectl-whoami /usr/local/bin

Listing 2-13Plugin installation

现在,重新运行kubectl插件列表命令。

$ kubectl plugin list
The following compatible plugins are available:

/usr/local/bin/kubectl-whoami

Listing 2-14Installed plugins

说明kubectl可以发现插件。让我们测试一下,看看它是如何工作的。

$ kubectl whoami
User: minikube

Listing 2-15kubectl whoami plugin

运行最后一个命令有两个关键点。第一点是kubectl whoami是一个扩展命令,在本地实现中不可用。但是,通过扩展功能,您可以运行自定义子命令。第二点是现在有可能检索信息,干扰kubectl的操作。

在下面的练习中,您将创建一个kubectl提示符命令,在 bash 提示符下显示当前的 Kubernetes 集群和用户名。

EXERCISE: KUBERNETES BASH PROMPT

处理一个 Kubernetes 集群很容易,但是当它变成日常事务中的几十个集群时就变得很麻烦了。在终端 bash 提示符中了解当前集群和用户有助于避免犯严重错误。我们将在每个带有(user @ cluster)信息的命令前显示一个字符串。

  1. Create a file with the name kubectl-prompt with the following content:

    #!/bin/bash
    
    currentContext=$(kubectl config current-context)
    prompt="(%s @ %s) > "
    template="{{ range .contexts }}{{ if eq .name \"$currentContext\" }}{{ printf \"$prompt\" .context.user .context.cluster}}{{ end }}{{ end }}"
    kubectl config view --template="$template"
    
    

    该脚本检查kubeconfig中的所有上下文,并检索集群和用户名字段。

  2. 将文件移动到PATH环境变量中的文件夹,并使其可执行:

    sudo chmod +x ./kubectl-prompt
    sudo mv ./kubectl-prompt /usr/local/bin
    
    
  3. 使用以下命令测试插件:

    $ kubectl prompt
    (minikube @ minikube) >
    
    
  4. 设置提示环境变量:

    $ export PS1=$(kubectl prompt)
    (minikube @ minikube) >
    
    

从现在开始,每个终端命令都将提供提示符。哪个集群和用户处于控制中,这将始终在您的视线之内。

插件扩展了kubectl并帮助您在与 Kubernetes 集群交互时实现更多。预计在操作集群时会有类似的困难,这导致开发类似的插件。在下一节中,重点将放在kubectl的插件库以及如何使用它。

插件库:krew

Kubernetes 社区有一个名为krewkubectl插件管理器。插件管理器帮助发现、安装和更新开源和社区维护的插件。目前,krew上分布着 100 多个插件。因此,在创建一个新的插件库之前,检查一下插件库是值得的。Kubernetes 社区中可能已经有人开发了相同的功能并发布了它。

让我们开始安装krew,一个kubectl插件本身,并发现一些存储库插件。在终端运行该命令下载krew

curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/krew.tar.gz"
tar zxf krew.tar.gz

Listing 2-16Downloading krew

现在,根据操作系统安装二进制文件。

# Linux
./krew-linux_amd64 install krew
# macOS
./krew-darwin_amd64 install krew

Adding "default" plugin index from https://github.com/kubernetes-sigs/krew-index.git.
Updated the local copy of plugin index.
Installing plugin: krew
Installed plugin: krew
\
 | Use this plugin:
 | kubectl krew
 | Documentation:
 | https://krew.sigs.k8s.io/
 | Caveats:
 | \
 | | krew is now installed! To start using kubectl plugins, you need to add
 | | krew's installation directory to your PATH:
 | |
 | | * macOS/Linux:
 | | - Add the following to your ~/.bashrc or ~/.zshrc:
 | | export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
 | | - Restart your shell.
 | |
 | | * Windows: Add %USERPROFILE%\.krew\bin to your PATH environment variable
 | |
 | | To list krew commands and to get help, run:
 | | $ kubectl krew
 | | For a full list of available plugins, run:
 | | $ kubectl krew search
 | |
 | | You can find documentation at
 | | https://krew.sigs.k8s.io/docs/user-guide/quickstart/.
 | /
/

Listing 2-17Downloading krew

最后,将 krew 安装目录添加到路径中。

export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"

Listing 2-18Path expansion

现在,我们可以通过调用一个kubectl插件来测试它。

$ kubectl krew
krew is the kubectl plugin manager.
You can invoke krew through kubectl: "kubectl krew [command]..."

Usage:
  kubectl krew [command]

Available Commands:
  help        Help about any command
  index       Manage custom plugin indexes
  info        Show information about an available plugin
  install     Install kubectl plugins
  list        List installed kubectl plugins
  search      Discover kubectl plugins
  uninstall   Uninstall plugins
  update      Update the local copy of the plugin index
  upgrade     Upgrade installed plugins to newer versions
  version     Show krew version and diagnostics

Flags:
  -h, --help      help for krew
  -v, --v Level   number for the log level verbosity

Use "kubectl krew [command] --help" for more information about a command.

Listing 2-19kubectl krew output

现在可以搜索、安装和升级由krew管理的插件。在krew网站上可以获得最新的插件列表,包括名称、描述和 GitHub 流行度,如图 2-3 所示。

img/503015_1_En_2_Fig3_HTML.jpg

图 2-3

krew 插件列表

让我们假设您在 Kubernetes 中运行一个 web 应用,它在实例前面有一个服务。要访问和测试应用,您需要到达服务端点。幸运的是,Kubernetes 社区有一个插件可以完成这项任务。open-svc是通过本地代理服务器在浏览器中打开指定服务 URL 的kubectl插件。你可以通过krew安装。

$ kubectl krew install open-svc
Updated the local copy of plugin index.
Installing plugin: open-svc
Installed plugin: open-svc
\
 | Use this plugin:
 | kubectl open-svc
 | Documentation:
 | https://github.com/superbrothers/kubectl-open-svc-plugin
/
WARNING: You installed plugin "open-svc" from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.

Listing 2-20Installing open-svc plugin

注意如果您还没有为您的集群启用 Kubernetes 仪表板,您可以运行minikube dashboard来安装它。

现在,让我们使用kubectl open-svc插件打开 Kubernetes 仪表板。

$ kubectl open-svc kubernetes-dashboard -n kubernetes-dashboard
Starting to serve on 127.0.0.1:8001
Opening service/kubernetes-dashboard in the default browser...

Listing 2-21open-svc plugin in action

该命令应在浏览器中打开仪表板,如图 2-4 所示。

img/503015_1_En_2_Fig4_HTML.jpg

图 2-4

忽必烈的控制板

只需要几个命令就可以从存储库中安装新插件并开始使用它们。因此,在从头开始创建社区之前,检查社区已经开发了什么是有用的。

关键要点

  • kubectl是与 Kubernetes API 交互的官方客户端。

  • 本机命令对于操作 Kubernetes 集群是必不可少的。

  • 通过创建插件,可以用新命令扩展kubectl

  • kubectl插件是第三方二进制,由kubectl执行。

  • 有一个由社区维护的插件库,名为krew

在下一章中,我们将继续 API 流扩展,并学习如何使用认证、授权和准入控制来扩展流。

Footnotes 1

https://github.com/kubernetes/kubectl

 

三、API 流扩展

把鸡蛋放在一个篮子里没关系,只要你能控制那个篮子会发生什么。

—埃隆·马斯克

商业巨头、工业设计师、工程师

Kubernetes 是安全、可靠、可扩展的云原生应用之家。Kubernetes API 流使得认证请求、决定授权和通过许可步骤成为可能。这个流程使 Kubernetes 成为一个受保护的环境,同时让您定义什么是允许的,什么是不允许的。在这一章中,我们将着重于扩展 Kubernetes API 流,并干预我们的自定义决策。在本章结束时,你将对 API 流有一个自信的看法,并对扩展 webhooks 有实际操作经验。

我们先来总结一下 Kubernetes API 流程及其扩展点。

忽必烈 API Flow

您可以使用kubectl、客户端库或者直接发送 REST 请求来连接和使用 Kubernetes API。对 API 的每个请求都要经过认证、授权和几个准入控制阶段。这三个阶段都通过 webhooks 提供了扩展点,将在本章中讨论。流程和扩展点可在图 3-1 中说明。

img/503015_1_En_3_Fig1_HTML.jpg

图 3-1

忽必烈 API flow

证明

身份验证是验证传入 API 请求身份的第一步。Kubernetes 使用客户端证书、不记名令牌、基本身份验证和身份验证插件来审查请求。此外,它能够同时运行多个授权码。普遍的 Kubernetes 装置预计具有以下特点:

  • 服务帐户用户的服务帐户令牌

  • 至少一种其他方法,如用于用户身份验证的客户端证书、承载令牌或基本 auth

您可以通过添加 webhook 身份验证器来验证不记名令牌,从而扩展身份验证机制。Kubernetes 将向远程服务发送一个 JSON 请求,远程服务充当您的 webhook 服务。在远程服务中,您将验证令牌并决定是否允许请求。

批准

授权是确定用户是否可以读取、写入或更新 API 资源的第二个阶段。Kubernetes 中的授权模块检查请求的用户、组、HTTP 动词、资源和名称空间属性来验证它们。像身份验证一样,可以使用多个授权模块,如基于属性的访问控制(ABAC)、基于角色的访问控制(RBAC)和 webhooks。您可以通过添加新的 webhook 模块来扩展授权。您的定制 webhook 服务接收带有访问检查数据的 HTTP POST 请求。评估完成后,webhook 服务会根据允许与否发回响应。

准入控制

传入请求的第三和最后阶段是准入控制模块。准入控制器是一个代码,用于在身份验证和授权之后、持久化到存储之前拦截到达 Kubernetes API 服务器的请求。与前面的阶段类似,可以依次运行多个准入控制器。然而,早期阶段之间有两个主要区别。第一个问题是,接纳控制器不是简单地应用于读取对象的 GET 请求。第二个区别是这些模块可以修改请求和相关的实体。因此,他们可以验证规范值(如容器图像)或设置默认值(如 CPU 请求)。

各种准入控制器已经打包到kube-apiserver中,并根据 Kubernetes 版本启用或禁用。让我们直接从kube-apiserver二进制文件中查看控制器列表:

$ docker run -it --rm k8s.gcr.io/kube-apiserver:v1.19.0 kube-apiserver --help | grep enable-admission-plugins
...
      --enable-admission-plugins strings admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, ...). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, ...

Listing 3-1kube-apiserver plugin listing

Note

输出被连接起来,因为它长得令人难以置信,我们不会一一介绍每个准入插件。如果您需要关于插件的更多信息,您可以查看参考文档。

除了kube-apiserver中的默认准入控制器之外,还可以在运行时添加新的控制器作为 webhooks。与身份验证和授权插件不同,当集群运行时,可以添加或删除准入控制器;因此,他们被称为动态接纳控制器

让我们通过开发 webhooks 和配置 Kubernetes 集群来扩展认证流程。

身份验证网页挂钩

身份验证 webhooks 通过外部安全逻辑扩展了 Kubernetes API 流。Kubernetes 中的 Webhooks 是对外部系统的 HTTP 回调。当集群中发生特定事件时,Kubernetes API 服务器通过 HTTP POST 向外部服务发送结构化请求。webhook 服务器应该返回一个结构化的响应,以便 Kubernetes API 服务器继续运行。运行认证 webhooks 需要配置两个基本部分: Kubernetes API 服务器webhook 服务器。让我们从 Kubernetes API 服务器开始,让它知道作为 webhook 连接到哪里。

服务器 API 配置库

Kubernetes API 服务器运行在控制平面中,需要知道作为 webhook 连接到哪里。通过kube-apiserver二进制的标志和配置文件设置配置。因此,集群管理员(很可能是您)应该处理设置。认证 webhook 配置的kube-apiserver有两个基本标志:

  • --authentication-token-webhook-config-file:描述如何访问远程 webhook 服务的配置文件

  • --authentication-token-webhook-cache-ttl:多长时间缓存认证决策,默认为两分钟

Note

还有一个名为--authentication-token-webhook-version的版本标志。它决定是否使用authentication.k8s.io/v1beta1authentication.k8s.io/v1 TokenReview对象来发送/接收来自 webhook 的信息。默认为v1beta1,在本章中使用。

authentication-token-webhook-config-file标志没有默认值,需要一个类似于kubeconfig的配置文件。

apiVersion: v1
kind: Config
clusters:
  - name: remote-auth-service
    cluster:
      certificate-authority: /path/to/ca.pem
      server: https://extend.k8s.io/authenticate
users:
  - name: remote-auth-service-user
    user:
      client-certificate: /path/to/cert.pem
      client-key: /path/to/key.pem
current-context: webhook
contexts:
- context:
    cluster: remote-auth-service
    user: remote-auth-service-user
  name: webhook

Listing 3-2Authentication token webhook config example

当承载令牌认证激活时,Kubernetes API 服务器连接到集群中定义的服务器,并在必要时使用certificate-authority。此外,API 服务器利用client-certificateclient-key与 webhook 服务器进行安全通信。现在,让我们继续 webhook 服务器和 Kubernetes API 之间的通信。

web 手册服务器

当 API 服务器配置了 webhook 令牌认证时,它将发送一个带有TokenReview对象的 JSON 请求。示例TokenReview对象可以按如下方式构造。

{
  "apiVersion": "authentication.k8s.io/v1beta1",
  "kind": "TokenReview",
  "spec": {
    "token": "0x123...",
  }
}

Listing 3-3TokenReview object

webhook 服务器验证传入的令牌并收集用户信息。远程服务器必须填写 TokenReview 对象的状态字段并发回数据。不记名令牌的成功验证将返回以下令牌 Review 作为示例。

{
   "apiVersion": "authentication.k8s.io/v1beta1",
   "kind": "TokenReview",
   "status": {
      "authenticated": true,
      "user":{
         "username": "user@k8s.io",
         "uid": "21",
         "groups":[ "system", "qa" ]
      }
   }
}

Listing 3-4TokenReview with a successful status

用户名、UID 和组是经过验证的用户的标识符。该信息对于授权阶段决定谁有权访问哪个组是至关重要的。当令牌验证失败时,webhook 服务器应该返回一个类似如下的请求。

{
  "apiVersion": "authentication.k8s.io/v1beta1",
  "kind": "TokenReview",
  "status": {
    "authenticated": false,
    "error": "Credentials are not validated"
  }
}

Listing 3-5TokenReview with a failed status

当 webhook 服务器拒绝用户时,kube-apiserver使用TokenReview状态的错误信息。在图 3-2 中可以总结出 webhook 和 Kubernetes API 服务器之间的 TokenReview 消息流。

img/503015_1_En_3_Fig2_HTML.jpg

图 3-2

认证消息流

webhook 服务器可以在 Kubernetes 集群的内部或外部。换句话说,您可以在 Kubernetes 集群内部运行一个服务器,并将其用作 webhook 服务器。Kubernetes API 服务器使用 HTTPS 连接到 webhook 服务器;因此,您还需要设置 TLS 证书。或者,您可以将 webhook 服务器设置在集群外部,并使其对外部世界可用。这两个选项的关键点是启动并运行 webhook 服务器,因为身份验证流程依赖于它。在下面的练习中,您将把一个无服务器的 webhook 部署到 Google Cloud,并配置一个本地 minikube 集群,将其用作身份验证端点。

EXERCISE: SERVERLESS AUTHENTICATION WEBHOOK

本练习中的 webhook 服务器将运行在 Google Cloud 上,作为其无服务器平台的一部分。您将开始创建一个云功能,并将其部署到 Google Cloud。然后,您将配置本地 minikube 集群,以使用无服务器功能的地址作为身份验证 webhook。

img/503015_1_En_3_Fig4_HTML.jpg

图 3-4

创建功能

img/503015_1_En_3_Fig3_HTML.jpg

图 3-3

GCP 云函数

  1. 在如图 3-4 所示的“创建函数”视图中,填写 name 字段,选择“允许未授权调用,然后点击下一步

  2. 打开谷歌云控制台,前往计算云功能。在功能列表视图中点击创建功能,如图 3-3 所示。

记下触发器 URL,因为您将在步骤 5 中将其用作 SERVERLESS_ENDPOINT 环境变量。

img/503015_1_En_3_Fig5_HTML.jpg

图 3-5

功能的部署

  1. In the “Code” view, select Go as runtime and fill “Entry Point” field with Authenticate. Authenticate is the name of the function that Google Cloud will call when the serverless endpoint is reached. Change the contents of the function.go with the following content:

    package authenticate
    
    import (
          "encoding/json"
          "errors"
          "log"
          "net/http"
          "strings"
    
          authentication "k8s.io/api/authentication/v1beta1"
    )
    
    func Authenticate(w http.ResponseWriter, r *http.Request) {
    
          decoder := json.NewDecoder(r.Body)
          var tr authentication.TokenReview
          err := decoder.Decode(&tr)
          if err != nil {
                handleError(w, err)
                return
          }
    
          user, err := logon(tr.Spec.Token)
          if err != nil {
                handleError(w, err)
                return
          }
    
          log.Printf("[Success] login as %s", user.username)
    
          w.WriteHeader(http.StatusOK)
          trs := authentication.TokenReviewStatus{
                Authenticated: true,
                User: authentication.UserInfo{
                      Username: user.username,
                      Groups:   []string{user.group},
                },
          }
          tr.Status = trs
          json.NewEncoder(w).Encode(tr)
    }
    
    func handleError(w http.ResponseWriter, err error) {
    
          log.Println("[Error]", err.Error())
    
          tr := new(authentication.TokenReview)
          trs := authentication.TokenReviewStatus{
                Authenticated: false,
                Error: err.Error(),
          }
          tr.Status = trs
    
          w.WriteHeader(http.StatusUnauthorized)
          json.NewEncoder(w).Encode(tr)
    
    }
    
    func logon(token string) (*User, error) {
          data := strings.Split(token, ";")
          if len(data) < 3 {
                return nil, errors.New("no token data")
          }
    
          for _, u := range allowed {
                if u.group == data[0] && u.username == data[1] && u.password == data[2] {
                      return &u, nil
                }
          }
    
          return nil, errors.New("no user found")
    }
    
    type User struct {
          username string
          password string
          group    string
    }
    
    var allowed = []User{
          {
                username: "minikube-user",
                group:    "system:masters",
                password: "mysecret",
          },
    }
    
    

    这个文件有一个Authenticate HTTP 端点来解析TokenReview数据,登录用户,并将其发送回来。它使用logon助手函数来搜索允许的用户。被允许的用户只有一个:minikube-user,其有效令牌为system:masters;minikube-user;mysecret

    Change the contents of go.mod as follows:

    module extend.k8s.io/authenticate
    
    go 1.14
    
    require k8s.io/api v0.19.0
    
    

    function.go中,我们使用的是 Kubernetes Go 客户端库;因此,我们将其列为k8s.io/api,版本v0.19.0的需求。

    在图 3-5 中点击页面底部的部署

Note:为了构建和部署该功能,如果您之前没有这样做,您需要在云控制台 API 库视图中启用云构建 API。

img/503015_1_En_3_Fig7_HTML.jpg

图 3-7

功能日志

img/503015_1_En_3_Fig6_HTML.jpg

图 3-6

成功部署

  1. Create a local webhook config file serverless-authn.yaml with the following content:

    apiVersion: v1
    kind: Config
    clusters:
      - name: serverless-authn
        cluster:
          server: SERVERLESS_ENDPOINT
    users:
      - name: authn-user
    current-context: webhook
    contexts:
    - context:
        cluster: serverless-authn
        user: authn-user
      name: webhook
    
    

    不要忘记用步骤 2 中的 URL 更改SERVERLESS_ENDPOINT

  2. 将文件移动到 minikube 文件:

    mkdir -p $HOME/.minikube/files/var/lib/minikube/certs
    mv serverless-authn.yaml $HOME/.minikube/files/var/lib/minikube/certs/serverless-authn.yaml
    
    
  3. 使用额外的标志启动 minikube 集群:

    img/503015_1_En_3_Figa_HTML.png

  4. 创建一个新的空用户并在当前上下文中使用:

    $ kubectl config set-credentials auth-test
    User "auth-test" set.
    $ kubectl config set-context --current --user=auth-test
    Context "minikube" modified.
    
    
  5. Run kubectl with the valid token and check the result:

    $ kubectl get nodes --token="system:masters;minikube-user;mysecret"
    NAME       STATUS   ROLES    AGE    VERSION
    minikube   Ready    master   116s   v1.19.2
    
    

    正如预期的那样,Kubernetes API 发送列出节点的输出。

  6. 使用随机令牌运行kubectl并检查结果:

    $ kubectl get nodes --token="xyz"
    error: You must be logged in to the server (Unauthorized)
    
    
  7. 检查 Google Cloud 中的无服务器功能日志,并查看 webhook 的运行情况,如图 3-7 所示。

  8. 在如图 3-6 所示的功能列表视图中等待,直到旁边出现绿色复选标记。

日志显示成功的(第一次)和失败的(第二次)登录活动。

在本练习中,您已经公开了一个公共函数,并在 Kubernetes 集群中使用了它。在您的生产设置中,建议使用受保护的功能。

在下一节中,我们将使用定制的授权模块来扩展 Kubernetes API 流。我们将学习 webhook 和 Kubernetes API 服务器需求,然后实现自定义决策逻辑来决定谁可以访问或修改集群中的资源。

授权网页挂钩

授权 webhooks 扩展了 Kubernetes API 的访问控制,以实现定制策略。当请求通过身份验证阶段时,授权模块按顺序评估属性。如果任何授权模块批准或拒绝请求,结果将立即返回。如果请求被批准,API 请求继续流程,并进入下一个阶段。与身份验证阶段一样,运行授权 webhooks 有两种基本配置:Kubernetes API 服务器和 webhook 服务器。

服务器 API 配置库

kube-apiserver具有定义授权模式和 webhook 配置的标志。授权模式通过--authorization-mode标志设置,默认值为AlwaysAllow。换句话说,默认情况下,Kubernetes API 允许所有经过身份验证的请求。但是,在典型的 Kubernetes 设置中,启用了以下授权模式:RBAC 和节点。因此,要添加 webhook 授权,您需要通过添加 Webhook 来更新标志值。配置授权 webhook 操作有三个基本标志:

  • --authorization-webhook-config-file:描述如何访问和查询远程服务的配置文件。该标志类似于认证中的标志,并且需要与kubeconfig相同的配置。确保授权 webhook 服务器地址和证书数据(如有必要)正确无误。

  • --authorization-webhook-cache-authorized-ttl:缓存已验证请求的持续时间;默认值为五分钟。

  • --authorization-webhook-cache-unauthorized-ttl:缓存无效请求的持续时间;默认值为 30 秒。

Note

还有一个名为--authorization-webhook-version的版本标志。它设置了authorization.k8s.io SubjectAccessReview的应用编程接口版本,以发送到网络钩子并从其获得期望。缺省值为v1beta1并在本章中使用。

web 手册服务器

Kubernetes API 服务器通过发送一个SubjectAccessReview对象来描述要检查的动作,从而调用 webhook 服务器。发送的 JSON 对象包含关于资源、用户和请求属性的信息。用户ece获取名称空间default中的 pod 的示例SubjectAccessReview具有以下结构。

{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "spec": {
    "resourceAttributes": {
      "namespace": "default",
      "verb": "get",
      "group": "",
      "resource": "pods"
    },
    "user": "ece"
  }
}

Listing 3-6SubjectAccessReview for pod listing

当 Kubernetes API 中的非资源路径被调用时,比如/version/metrics,nonResourceAttributes字段被发送到 webhook 服务器。例如,当用户nursin调用version端点时,Kubernetes API 服务器将发布下面的SubjectAccessReview

{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "spec": {
    "nonResourceAttributes": {
      "path": "/version",
      "verb": "get"
    },
    "user": "nursin"
  }
}

Listing 3-7SubjectAccessReview for version information

webhook 服务器通过填充status字段来响应SubjectAccessReview对象。如果 webhook 服务器接受请求,它可以很容易地将以下数据发送回 Kubernetes API 服务器。

{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "status": {
    "allowed": true
  }
}

Listing 3-8Accepted response

另一方面,在 webhook 服务器中有两种方法可以拒绝请求。第一种方法只表示请求不是如下的allowed

{
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "status": {
    "allowed": false,
    "reason": "user has no access"
  }
}

Listing 3-9Rejected response

当只有allowed字段被设置为假时,也检查其他授权模块是否允许它。如果没有一个授权模块允许该请求,则 API 服务器会拒绝该请求。第二种方法是立即拒绝任何请求,绕过剩余的授权模块。响应数据与前一个相似,只是增加了一个简单的内容。

 {
  "apiVersion": "authorization.k8s.io/v1beta1",
  "kind": "SubjectAccessReview",
  "status": {
    "allowed": false,
    "denied": true,
    "reason": "user has no access"
  }
}

Listing 3-10Rejected and denied response

Kubernetes API 和 authorization webhook 服务器之间的消息流可以总结在图 3-8 中。

img/503015_1_En_3_Fig8_HTML.jpg

图 3-8

授权消息流

尽管消息流看起来很简单,但是您将在 webhook 服务器中实现的逻辑是没有限制的。您可以设计一个授权系统来限制特定组的用户执行特定的操作。让我们假设你有两个团队,开发生产,以及一个用于发布的持续部署(CD) 系统。可以创建一个授权 webhook,让开发团队只访问阅读框。类似地,您可以限制生产团队更新部署,并且只允许技术用户从 CD 到创建新的部署。考虑到团队成员是在任何其他外部系统(如 LDAP 或 GitHub)中定义的,webhook 服务器将拥有相关的逻辑并扩展 Kubernetes 授权。

在下面的练习中,您将创建一个无服务器授权 webhook,使 Kubernetes 中的名称空间成为只读的。用户只能读取、列出或查看资源,但不能在受保护的命名空间中更新、创建或删除资源。

EXERCISE: AUTHORIZATION WEBHOOK FOR READ-ONLY NAMESPACE

在本练习中,您将在 Google Cloud Functions 中开发一个无服务器 webhook。webhook 将做出授权决定,将名称空间protected设为只读。然后,您将启动一个本地 minikube 集群,并将其配置为使用无服务器端点作为授权 webhook。

img/503015_1_En_3_Fig10_HTML.jpg

图 3-10

创建功能

img/503015_1_En_3_Fig9_HTML.jpg

图 3-9

GCP 云函数

  1. 在图 3-10 的“创建函数”视图中,填写名称字段并选择“允许未认证调用”,然后点击下一步。

  2. 打开谷歌云控制台,在主菜单中点击计算云功能。在图 3-9 的函数列表视图中点击“创建函数”。

在“代码”视图中,选择 Go as runtime,并用Authorize填充“入口点”字段。当到达无服务器端点时,它是我们的部署中要调用的函数。用以下内容更改function.go的内容:

package authorize

import (
      "encoding/json"
      "fmt"
      "log"
      "net/http"

      authorization "k8s.io/api/authorization/v1beta1"
)

const NAMESPACE = "protected"

func Authorize(w http.ResponseWriter, r *http.Request) {

      decoder := json.NewDecoder(r.Body)
      var sar authorization.SubjectAccessReview
      err := decoder.Decode(&sar)
      if err != nil {
            log.Println("[Error]", err.Error())

            sar := new(authorization.SubjectAccessReview)
            status := authorization.SubjectAccessReviewStatus{
                  Allowed: false,
                  Reason:  err.Error(),
            }
            sar.Status = status

            w.WriteHeader(http.StatusUnauthorized)
            json.NewEncoder(w).Encode(sar)
            return
      }

      if sar.Spec.ResourceAttributes != nil {
            v := sar.Spec.ResourceAttributes.Verb
            n := sar.Spec.ResourceAttributes.Namespace

            if n == NAMESPACE && (v == "create" || v == "delete" || v == "update") {

                  log.Printf("[Not Allowed] %s in namespace %s", sar.Spec.ResourceAttributes.Verb, NAMESPACE)

                  response := new(authorization.SubjectAccessReview)
                  status := authorization.SubjectAccessReviewStatus{
                        Allowed: false,
                        Denied:  true,
                        Reason:  fmt.Sprintf("%s is not allowed in the namespace: %s", sar.Spec.ResourceAttributes.Verb, NAMESPACE),
                  }
                  response.Status = status
                  json.NewEncoder(w).Encode(response)
                  return
            }
      }

      response := new(authorization.SubjectAccessReview)
      status := authorization.SubjectAccessReviewStatus{
            Allowed: true,
      }
      response.Status = status
      json.NewEncoder(w).Encode(response)
}

在这个文件中,只有一个名为Authorize的函数。它是一个 HTTP 处理程序,用于解析传入的SubjectAccessReview数据。如果传入的数据有ResourceAttributes,它将检查名称空间是否为protected,动词是否为createdeleteupdate。当发现这样的请求时,它通过发送Allowed: falseDenied: true来拒绝。对于所有其他请求,它允许请求并让其他授权模块决定。

go.mod的内容更改如下:

module extend.k8s.io/authorize

go 1.13

require k8s.io/api v0.19.0

function.go中,我们使用的是 Kubernetes Go 客户端库;因此,我们将其列为对k8s.io/api,版本v0.19.0的依赖。

在图 3-11 中点击页面底部的部署

img/503015_1_En_3_Fig11_HTML.jpg

图 3-11

功能的部署

img/503015_1_En_3_Fig12_HTML.jpg

图 3-12

成功部署

  1. 在图 3-12 中的功能列表视图中等待,直到旁边出现绿色复选标记。

用以下内容创建一个本地 webhook 配置文件serverless-authz.yaml:

apiVersion: v1
kind: Config
clusters:
  - name: serverless-authz
    cluster:
      server: SERVERLESS_ENDPOINT
users:
  - name: authz-user
current-context: webhook
contexts:
- context:
    cluster: serverless-authz
    user: authz-user
  name: webhook

不要忘记用步骤 2 中的 URL 更改SERVERLESS_ENDPOINT

img/503015_1_En_3_Fig13_HTML.jpg

图 3-13

功能日志

  1. 将文件移动到 minikube 文件:

    $ mkdir -p $HOME/.minikube/files/var/lib/minikube/certs
    
    $ mv serverless-authz.yaml $HOME/.minikube/files/var/lib/minikube/certs/serverless-authz.yaml
    
    
  2. 使用额外的标志启动 minikube 集群:

    img/503015_1_En_3_Figb_HTML.png

    该命令使用两个额外的配置参数启动本地 minikube 集群。第一个配置将 webhook 添加到授权模式中,第二个配置指出了第 3 步中配置文件的位置。

  3. Check what the user is allowed to do with the following commands:

    $ kubectl auth can-i create deployments --as developer
    yes
    
    

    It shows that it is possible to create deployments in the default namespace.

    $ kubectl auth can-i create deployments --as developer --namespace protected
    
    no - create is not allowed the namespace protected
    
    $ kubectl auth can-i delete secrets --as developer --namespace protected
    
    no - delete is not allowed in the namespace protected
    
    

    However, it is not allowed to create deployments or delete secrets in the protected namespace. It ensures that the resources in the namespace stay as it is in a read-only mode.

    $ kubectl auth can-i list pods --as developer --namespace protected
    yes
    
    

    另一方面,可以在protected名称空间中列出 pod,这是我们在只读模式下想要的。

  4. 检查 Google Cloud 中的无服务器功能日志,并查看 webhook 的运行情况,如图 3-13 所示。

日志表明授权 webhook 不允许创建和删除请求。

在下一节中,我们将扩展 Kubernetes API 流的最后一个阶段:准入控制器。准入控制器是在将请求保存到etcd存储器之前检查或改变请求的最后步骤。我们将学习准入 webhook 的设置以及如何动态定义来扩展和实现定制需求。

动态准入控制器

准入控制器是 Kubernetes API 流中对象持久化之前的最后一个阶段。这些控制器拦截验证或改变资源的请求。已经有各种准入控制器打包成kube-apiserver二进制,增加了两个扩展点:MutatingAdmissionWebhookValidatingAdmissionWebhook。这些扩展点执行变异和验证准入控制 webhooks,这是在 Kubernetes API 中动态定义的。与身份验证和授权 webhooks 不同,您可以在集群启动和运行时创建、更新或删除准入控制器。因此,它们主要包含在“动态接纳控制器”一节中

准入网络挂钩对于 Kubernetes 控制平面及其操作至关重要。变异准入 webhooks 允许设置复杂的默认值或注入字段,而验证 webhooks 对于控制部署到集群中的内容至关重要。首先,变异的 web 钩子被串行调用,因为每个钩子都可以修改资源对象。然后,并行调用所有验证 webhooks 如果它们中的任何一个拒绝了请求,那么它就会被 API 服务器拒绝。

有两个方面需要配置和设置来扩展接纳控制机制:webhook 配置资源和 webhook 服务器。让我们首先关注 webhook 配置资源,让 Kubernetes API 知道在哪里以及何时调用 webhooks。

Webhook 配置资源

准入控制器的动态配置由ValidatingWebhookConfigurationMutatingWebhookConfiguration API 资源处理。可以为 pod 创建定义一个验证 webhook 的示例,如下所示。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: "validation.extend-k8s.io"
webhooks:
- name: "validation.extend-k8s.io"
  rules:
  - apiGroups:   [""]
    apiVersions: ["v1"]
    operations:  ["CREATE"]
    resources:   ["pods"]
    scope:       "Namespaced"
  clientConfig:
    url: "https://extend-k8s.io/validate"
  admissionReviewVersions: ["v1", "v1beta1"]
  sideEffects: None

Listing 3-11Webhook configuration example

API 资源有两个关键部分:rulesclientConfig。当 Kubernetes API 服务器收到一个符合规则的请求时,就会向在clientConfig中定义的 webhook 发出一个 HTTP 请求。例如,使用清单 3-11 中的定义,当创建一个新的 pod 时,API 服务器将调用 https://extend-k8s.io/validate

变异 webhook 配置是用具有类似结构的MutatingWebhookConfiguration资源完成的。创建机密时调用的示例 webhook 可以定义如下。

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: "mutation.extend-k8s.io"
webhooks:
- name: "mutation.extend-k8s.io"
  rules:
  - apiGroups:   [""]
    apiVersions: ["v1"]
    operations:  ["CREATE"]
    resources:   ["secrets"]
    scope:       "Namespaced"
  clientConfig:
    service:
      namespace: "extension"
      name: "mutation-service"
    caBundle: "Ci0tLS0tQk...tLS0K"
  admissionReviewVersions: ["v1", "v1beta1"]
  sideEffects: None

Listing 3-12Webhook configuration example

当接收到一个秘密创建请求时,Kubernetes API 服务器将到达在extension名称空间中运行的mutation-service的 443 端口。它将使用caBundle来验证 webhook 服务器的 TLS 证书。在下一节中,我们将介绍准入控制器 webhooks 之间的消息流。

web 手册服务器

Kubernetes API 服务器发送一个带有AdmissionReview对象的 POST 请求来定义请求及其属性。例如,当创建一个新的 pod 时,webhook 服务器将收到一个类似如下的项目。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "request": {

    "uid": "4b8bd269-bfc7-4dd5-8022-7ca57a334fa3",

    "name": "example-app",
    "namespace": "default",

    "operation": "CREATE",

    "kind": {"group":"","version":"v1","kind":"Pod"},
    "requestKind":  {"group":"","version":"v1","kind":"Pod"},

    "resource": {"group":"","version":"v1","resource":"pods"},
    "requestResource": {"group":"","version":"v1","resource":"pods"},

    "object": {"apiVersion":"v1","kind":"Pod",...},

    "userInfo": {
      "username": "minikube",
      "groups": ["system:authenticated"]
    },

    "options": {"apiVersion":"meta.k8s.io/v1","kind":"CreateOptions",...},
    "dryRun": false
  }
}

Listing 3-13AdmissionReview example

准入审查对象被合理地打包,因为它传输与请求和相关项目相关的所有信息。例如,在清单 3-13 中的request.object字段中有一个完整的 pod 定义。Webhook 服务器需要再次发送一个加载了响应字段的AdmissionReview对象。最小接受响应可以按如下方式构建。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true
  }
}

Listing 3-14Accepted admission review response

类似地,可以用以下数据发送一个简单的拒绝。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": false
  }
}

Listing 3-15Rejected admission review response

变异的 webhooks 也可以修改请求中的对象。因此,webhook 服务器应该在AdmissionReview响应中发送更改。Kubernetes 支持JSONPatch种改变资源领域的操作。例如,将一个部署的副本更改为 5 的JSONPatch可以如下构建:[{"op": "replace", "path": "/spec/replicas", "value": 5}]。当补丁在AdmissionReview内部传输时,将使用base64进行如下编码。

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true,
    "patchType": "JSONPatch",
    "patch": "W3sib3AiOiAicmVwbGFjZSIsICJwYXRoIjogIi9zcGVjL3JlcGxpY2FzIiwgInZhbHVlIjogNX1d"
  }
}

Listing 3-16Accepted admission review response with patch

当 Kubernetes API 获得带有补丁的响应时,它将在资源上应用更改,并继续处理下一个准入控制器。webhook 服务器和 Kubernetes API 之间的消息摘要可以总结在图 3-14 中。

img/503015_1_En_3_Fig14_HTML.jpg

图 3-14

准入 webhook 消息流

变异和验证 webhooks 都是 API 流中的关键组件,因为它们可以通过编程方式改变资源,接受或拒绝请求。当疏忽或设计不当使用时,它会很快造成混乱。对于可靠的准入控制设置,有三个最佳实践可以遵循:

  • 幂等:变异的 webhooks 应该是幂等的;换句话说,可以多次调用 admission webhook,而不会改变第一次运行后的结果。

  • 可用性:准入 webhooks 作为 Kubernetes API 操作的一部分被调用。因此,它们应该像所有其他 webhook 服务器一样,尽快评估并返回响应,以最小化总延迟。

  • 死锁:如果 webhook 端点在集群内部运行,它们会干扰集群的资源,比如 pod、secrets 或 volumes。因此,建议不要在 webhook 的名称空间上运行准入控制器。

在以下练习中,您将首先创建一个动态验证准入 webhook 来检查和验证容器图像。在第二个练习中,您将把环境变量注入到在特定名称空间中运行的带有可变许可 webhooks 的 pod 中。

EXERCISE: VALIDATING WEBHOOK FOR CONTAINER IMAGE CHECK

在本练习中,您将在 Google Cloud Functions 中开发一个无服务器 webhook。webhook 将通过评估容器图像来验证 pod 创建请求。它将只允许包含 nginx 的容器图像,而拒绝所有其他容器。然后,您将启动一个 GKE 集群,并将集群的名称空间配置为使用验证 webhook。

img/503015_1_En_3_Fig15_HTML.jpg

图 3-15

GCP 云壳

  1. 创建一个名称空间,并将其标记为:

    $ kubectl create namespace nginx-only
    namespace/nginx-only created
    $ kubectl label namespace nginx-only nginx=true
    namespace/nginx-only labeled
    
    
  2. Create a file with the name validating-webhook.yaml with the following content:

    apiVersion: admissionregistration.k8s.io/v1
    kind: ValidatingWebhookConfiguration
    metadata:
      name: nginx.validate.extend.k8s
    webhooks:
    - name: nginx.validate.extend.k8s
      namespaceSelector:
          matchLabels:
            nginx: "true"
      rules:
      - apiGroups:   [""]
        apiVersions: ["v1"]
        operations:  ["CREATE"]
        resources:   ["pods"]
        scope:       "Namespaced"
      clientConfig:
        url: https://us-central1-extend-k8s.cloudfunctions.net/validate
      admissionReviewVersions: ["v1", "v1beta1"]
      sideEffects: None
      timeoutSeconds: 10
    
    

    该文件将创建一个验证 webhook,当在标有nginx=true的名称空间中创建新的 pod 时,将调用该 web hook。不要忘记将url更改为步骤 3 中复制的那个。

    Deploy the validating webhook conifguration with the following code:

    $ kubectl apply -f validating-webhook.yaml
    validatingwebhookconfiguration.admissionregistration.k8s.io/nginx.validate.extend.k8s created
    
    
  3. Create pod with nginx image in the nginx-only namespace:

    $ kubectl run --generator=run-pod/v1 nginx --image=nginx --namespace=nginx-only
    pod/nginx created
    
    

    由于许可 webhook 只允许运行nginx图像,因此创建了 pod。

  4. Create a pod with busybox image in the nginx-only namespace:

    $ kubectl run --generator=run-pod/v1 busybox --image=busybox --namespace=nginx-only
    Error from server: admission webhook "nginx.validate.extend.k8s" denied the request without explanation
    
    

    许可 webhook 拒绝了指定名称空间中的图像名称busybox。它显示了 webhook 服务器和 Kubernetes API 服务器都被正确地配置为扩展验证准入控制器。

  5. 删除云功能和 Kubernetes 集群,避免额外的云费用:

    $ gcloud container clusters delete test-validation --region=us-central1
    The following clusters will be deleted.
     - [test-validation] in [us-central1]
    Do you want to continue (Y/n)?  Y
    Deleting cluster test-validation...done.
    
    $ gcloud functions delete validate
    Resource
    [projects/extend-k8s/locations/us-central1/functions/validate] will be deleted.
    Do you want to continue (Y/n)?  Y
    Waiting for operation to finish...done.
    Deleted
    
    
  6. Create a folder named validation and change the directory into it:

    $ mkdir validation
    
    $ cd validation
    
    

    Create a file named function.go in the terminal or open the editor inside Google Cloud Console. The file should have the following content:

    package validate
    
    import (
          "encoding/json"
          "log"
          "net/http"
          "regexp"
    
          admission "k8s.io/api/admission/v1"
          corev1 "k8s.io/api/core/v1"
          metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    )
    
    func Validation(w http.ResponseWriter, r *http.Request) {
    
          ar := new(admission.AdmissionReview)
          err := json.NewDecoder(r.Body).Decode(ar)
          if err != nil {
                handleError(w, nil, err)
                return
          }
    
          response := &admission.AdmissionResponse{
                UID:     ar.Request.UID,
                Allowed: true,
          }
    
          pod := &corev1.Pod{}
          if err := json.Unmarshal(ar.Request.Object.Raw, pod); err != nil {
                handleError(w, ar, err)
                return
          }
    
          re := regexp.MustCompile(`(?m)(nginx|nginx:\S+)`)
    
          for _, c := range pod.Spec.Containers {
    
                if !re.MatchString(c.Image) {
                      response.Allowed = false
                      break
                }
          }
    
          responseAR := &admission.AdmissionReview{
                TypeMeta: metav1.TypeMeta{
                      Kind:       "AdmissionReview",
                      APIVersion: "admission.k8s.io/v1",
                },
                Response: response,
          }
    
          json.NewEncoder(w).Encode(responseAR)
    }
    
    func handleError(w http.ResponseWriter, ar *admission.AdmissionReview, err error) {
    
          if err != nil {
                log.Println("[Error]", err.Error())
          }
    
          response := &admission.AdmissionResponse{
                Allowed: false,
          }
          if ar != nil {
                response.UID = ar.Request.UID
          }
    
          ar.Response = response
          json.NewEncoder(w).Encode(ar)
    }
    
    

    该文件有一个名为Validation的 HTTP 处理程序来解析传入的AdmissionReview对象并检查所有容器的图像。当它发现一个容器图像不符合nginx时,它将直接拒绝审查并发送响应。否则,它将通过发送Allowed: true来接受。

    Create another file named go.mod with the following content:

    module extend.k8s.io/validate
    
    go 1.13
    
    require (
      k8s.io/api v0.19.0
      k8s.io/apimachinery v0.19.0
    )
    
    

    function.go中,我们使用的是 Kubernetes Go 客户端库;因此,我们将其列为与k8s.io/apik8s.io/apimachinery,版本v0.19.0的依赖关系。

  7. Deploy the function with the following command:

    $ gcloud functions deploy validate --allow-unauthenticated --entry-point=Validation --trigger-http --runtime=go113
    ..
    entryPoint: Validation
    httpsTrigger:
      url: https://us-central1-extend-k8s.cloudfunctions.net/validate
    ...
    runtime: go113
    ...
    status: ACTIVE
    timeout: 60s
    ..
    versionId: '1'
    
    

    复制在以下步骤中使用的httpsTrigger URL。

  8. Create a Kubernetes cluster with the following command:

    $ gcloud container clusters create test-validation --num-nodes=1 --region=us-central1
    
    Creating cluster test-validation in us-central1...
    Cluster is being health-checked (master is healthy)...
    done.
    kubeconfig entry generated for test-validation.
    
    NAME             LOCATION     MASTER_VERSION    MASTER_IP     MACHINE_TYPE   NODE_VERSION      NUM_NODES  STATUS
    test-validation  us-central1  1.16.15-gke.4300  34.69.30.171  n1-standard-1  1.16.15-gke.4300  3          RUNNING
    
    

    注意为了创建一个 Kubernetes 集群,您需要在云控制台 API 库视图中启用 Kubernetes 引擎 API,如果您之前没有这样做的话。

  9. 打开谷歌云控制台,点击导航栏中的激活云壳。它应该在你的浏览器中加载一个终端来运行如图 3-15 所示的命令。

EXERCISE: MUTATING WEBHOOK FOR ENVIRONMENT VARIABLE INJECTION

在本练习中,您将在 Google Cloud Functions 中开发一个无服务器 webhook。在创建新的 pod 时,webhook 将改变传入的请求。它将为在名为debug=true的名称空间中创建的 pod 注入一个值为true的环境变量DEBUG。此外,您将启动并配置一个 GKE 集群,以查看 webhook 的运行情况。

img/503015_1_En_3_Fig16_HTML.jpg

图 3-16

GCP 云壳

  1. Create a folder named mutation and change the directory into it:

    $ mkdir mutation
    
    $ cd mutation
    
    

    Create a file named function.go in the terminal with the following content:

    package mutator
    
    import (
          "encoding/json"
          "log"
          "net/http"
    
          admission "k8s.io/api/admission/v1"
          corev1 "k8s.io/api/core/v1"
          metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    )
    
    func Mutation(w http.ResponseWriter, r *http.Request) {
    
          ar := new(admission.AdmissionReview)
          err := json.NewDecoder(r.Body).Decode(ar)
          if err != nil {
                handleError(w, nil, err)
                return
          }
          pod := &corev1.Pod{}
          if err := json.Unmarshal(ar.Request.Object.Raw, pod); err != nil {
                handleError(w, ar, err)
                return
          }
    
          for i := 0; i < len(pod.Spec.Containers); i++ {
                pod.Spec.Containers[i].Env = append(pod.Spec.Containers[i].Env, corev1.EnvVar{
                      Name:  "DEBUG",
                      Value: "true",
                })
          }
    
          containersBytes, err := json.Marshal(&pod.Spec.Containers)
          if err != nil {
                handleError(w, ar, err)
                return
          }
    
          patch := []JSONPatchEntry{
                {
                      OP:    "replace",
                      Path:  "/spec/containers",
                      Value: containersBytes,
                },
          }
    
          patchBytes, err := json.Marshal(&patch)
          if err != nil {
                handleError(w, ar, err)
                return
    
          }
    
          patchType := admission.PatchTypeJSONPatch
    
          response := &admission.AdmissionResponse{
                UID:       ar.Request.UID,
                Allowed:   true,
                Patch:     patchBytes,
                PatchType: &patchType,
          }
    
          responseAR := &admission.AdmissionReview{
                TypeMeta: metav1.TypeMeta{
                      Kind:       "AdmissionReview",
                      APIVersion: "admission.k8s.io/v1",
                },
                Response: response,
          }
    
          json.NewEncoder(w).Encode(responseAR)
    }
    
    type JSONPatchEntry struct {
          OP    string          `json:"op"`
          Path  string          `json:"path"`
          Value json.RawMessage `json:"value,omitempty"`
    }
    
    func handleError(w http.ResponseWriter, ar *admission.AdmissionReview, err error) {
    
          if err != nil {
                log.Println("[Error]", err.Error())
          }
    
          response := &admission.AdmissionResponse{
                Allowed: false,
          }
          if ar != nil {
                response.UID = ar.Request.UID
          }
    
          ar.Response = response
          json.NewEncoder(w).Encode(ar)
    }
    
    

    该函数有一个名为Mutation的 HTTP 处理程序来解析AdmissionReview并准备响应。它首先向 pod 中的所有容器添加环境变量,然后创建一个JSONPatch。最后,它发送一个带有补丁数据的批准的AdmissionReview响应。

    Create another file named go.mod with the following content:

    module extend.k8s.io/mutate
    
    go 1.13
    
    require (
      k8s.io/api v0.19.0
      k8s.io/apimachinery v0.19.0
    )
    
    

    function.go中,我们使用的是 Kubernetes Go 客户端库;因此,我们将其列为与k8s.io/apik8s.io/apimachinery,版本v0.19.0的依赖关系。

  2. Deploy the function with the following command:

    $ gcloud functions deploy mutate --allow-unauthenticated --entry-point=Mutation --trigger-http --runtime=go113
    ..
    entryPoint: Mutation
    httpsTrigger:
      url: https://us-central1-extend-k8s.cloudfunctions.net/mutate
    ...
    runtime: go113
    ...
    status: ACTIVE
    timeout: 60s
    ..
    versionId: '1'
    
    

    复制步骤 6 中使用的httpsTrigger URL。

  3. 使用以下命令创建一个 Kubernetes 集群:

    $ gcloud container clusters create test-mutation --num-nodes=1 --region=us-central1
    
    Creating cluster test-mutation in us-central1...
    Cluster is being health-checked (master is healthy)...
    done.
    kubeconfig entry generated for test-mutation.
    
    NAME             LOCATION     MASTER_VERSION    MASTER_IP     MACHINE_TYPE   NODE_VERSION      NUM_NODES  STATUS
    test-mutation  us-central1  1.16.15-gke.4300  34.122.242.6  n1-standard-1  1.16.15-gke.4300  3          RUNNING
    
    
  4. 创建一个名称空间,并将其标记为:

    $ kubectl create namespace testing
    namespace/testing created
    $ kubectl label namespace testing debug=true
    namespace/testing labeled
    
    
  5. Create a file with the name mutating-webhook.yaml with the following content. Do not forget to change the with the URL from Step 3:

    apiVersion: admissionregistration.k8s.io/v1
    kind: MutatingWebhookConfiguration
    metadata:
      name: debug.mutate.extend.k8s
    webhooks:
    - name: debug.mutate.extend.k8s
      namespaceSelector:
          matchLabels:
            debug: "true"
      rules:
      - apiGroups:   [""]
        apiVersions: ["v1"]
        operations:  ["CREATE"]
        resources:   ["pods"]
        scope:       "Namespaced"
      clientConfig:
        url: <httpsTrigger>
      admissionReviewVersions: ["v1", "v1beta1"]
      sideEffects: None
      timeoutSeconds: 10
    
    

    当在标有debug=true的名称空间中创建新的 pod 时,该文件将创建一个可变的 webhook 来调用。

    Deploy the mutating webhook configuration with the following code:

    $ kubectl apply -f mutating-webhook.yaml
    mutatingwebhookconfiguration.admissionregistration.k8s.io/debug.mutate.extend.k8s created
    
    
  6. Create pod in testing namespace:

    $ kubectl run --generator=run-pod/v1 nginx --image=nginx --namespace testing
    pod/nginx created
    
    

    Check for the environment variables in the nginx pod:

    $ kubectl --namespace testing exec nginx -- env | grep DEBUG
    DEBUG=true
    
    

    pod 有DEBUG=true环境变量,它是由变异的 webhook 注入的。它显示了 webhook 服务器和 Kubernetes API 服务器都被正确地配置为扩展可变准入控制器。

  7. Create pod in default namespace:

    $ kubectl run --generator=run-pod/v1 nginx --image=nginx
    pod/nginx created
    
    

    检查 nginx 窗格中的环境变量:

  8. 打开谷歌云控制台,点击导航栏中的激活云壳。它应该在你的浏览器中加载一个终端来运行如图 3-16 所示的命令。

$ kubectl exec nginx -- env | grep DEBUG

正如所料,在位于default名称空间中的 pod 中没有发现环境变量。

  1. 删除云功能和 Kubernetes 集群,避免额外的云费用:

    $ gcloud container clusters delete test-mutation --region=us-central1
    The following clusters will be deleted.
     - [test-mutation] in [us-central1]
    Do you want to continue (Y/n)?  Y
    Deleting cluster test-mutation...done.
    $ gcloud functions delete mutate
    Resource
    [projects/extend-k8s/locations/us-central1/functions/mutate] will be deleted.
    Do you want to continue (Y/n)?  Y
    Waiting for operation to finish...done.
    Deleted
    
    

关键要点

  • 对 Kubernetes API 的每个请求都要经过 Kubernetes API 流中的认证、授权和准入控制阶段。

  • Webhooks 可以扩展 Kubernetes API 流中的每个阶段。

  • 身份验证 webhooks 支持使用自定义逻辑和外部系统验证无记名令牌。

  • Authorization webhooks 支持验证用户并控制谁可以访问集群中的哪些资源。

  • 动态准入控制器可以修改相关资源并验证传入的 API 请求。

在下一章中,我们将使用自定义资源和自定义资源的自动化(即操作符)来扩展 Kubernetes API。