目录

文章目录

  • 目录
  • 实验环境
  • 实验软件
  • 1、准入控制器
  • 2、admission webhook 是什么
  • 3、创建配置一个 Admission Webhook
    • 1.编写 webhook
    • 2.构建
    • 3.部署
    • 4、配置 webhook
    • 5、测试
    • 6、部署 mutating webhook
  • 关于我
  • 最后

实验环境

实验环境:
1、win10,vmwrokstation虚机;
2、k8s集群:3台centos7.6 1810虚机,1个master节点,2个node节点k8s version:v1.22.2containerd://1.5.5

实验软件

1、准入控制器

Kubernetes 提供了需要扩展其内置功能的方法,最常用的可能是**自定义资源类型(CRD)自定义控制器(Operator)**了,除此之外,Kubernetes 还有一些其他非常有趣的功能,比如 admission webhooks 就可以用于扩展 API,用于修改某些 Kubernetes 资源的基本行为。

准入控制器是在对象持久化之前用于对 Kubernetes API Server 的请求进行拦截**(类似于后端开发中的中间件/拦截器)的代码段,在请求经过身份验证授权之后**放行通过。准入控制器可能正在 validatingmutating 或者都在执行。Mutating 控制器可以修改他们处理的资源对象;Validating 控制器不会。如果任何一个阶段中的任何控制器拒绝了请求,则会立即拒绝整个请求,并将错误返回给最终的用户。

这意味着有一些特殊的控制器可以拦截 Kubernetes API 请求,并根据自定义的逻辑修改或者拒绝它们。Kubernetes 有自己实现的一个控制器列表:https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do,当然你也可以编写自己的控制器,虽然这些控制器听起来功能比较强大,但是这些控制器需要被编译进 kube-apiserver,并且只能在 apiserver 启动时启动。

也可以直接使用 kube-apiserver 启动参数查看内置支持的控制器:

kube-apiserver --help |grep enable-admission-plugins

由于上面的控制器的限制,我们就需要用到动态的概念了,而不是和 apiserver 耦合在一起,Admission webhooks 就通过一种动态配置方法解决了这个限制问题。(注意:这个api和我们的聚合api还不是一回事儿)

2、admission webhook 是什么

在 Kubernetes apiserver 中包含两个特殊的准入控制器:MutatingAdmissionWebhookValidatingAdmissionWebhook,**这两个控制器将发送准入请求到外部的 HTTP 回调服务并接收一个准入响应。**如果启用了这两个准入控制器,Kubernetes 管理员可以在集群中创建和配置一个 admission webhook。

我们能修改的也就是MutatingAdmissionWebhooks和ValidatingAdmissionWebhooks这部分内容了。;这里也是可以有多个webhooks的;

整体的步骤如下所示:

  1. 检查集群中是否启用了 admission webhook 控制器,并根据需要进行配置。
  2. 编写处理准入请求的 HTTP 回调,回调可以是一个部署在集群中的简单 HTTP 服务,甚至也可以是一个 serverless 函数。
  3. 通过 MutatingWebhookConfigurationValidatingWebhookConfiguration 资源配置 admission webhook。

这两种类型的 admission webhook 之间的区别是非常明显的:validating webhooks 可以拒绝请求,但是它们却不能修改准入请求中获取的对象,而 mutating webhooks 可以在返回准入响应之前通过创建补丁来修改对象,如果 webhook 拒绝了一个请求,则会向最终用户返回错误。(注意:mutating webhooks其实包含了validating webhokks的功能,它也是可以直接拒绝请求的。)

⚠️现在非常火热的 Service Mesh 应用 istio 就是通过 mutating webhooks 来自动将 Envoy 这个 sidecar 容器注入到 Pod 中去的:https://istio.io/docs/setup/kubernetes/sidecar-injection/。

3、创建配置一个 Admission Webhook

⚠️注意下:这个实战是有一定的难度的,因为涉及到了代码。。。暂且搁置学习。。。

注意:这个部分的内容一定要掌握!!!

上面我们介绍了 Admission Webhook 的理论知识,接下来我们在一个真实的 Kubernetes 集群中来实际测试使用下,我们将创建一个 webhook 的 webserver,将其部署到集群中,然后创建 webhook 配置查看是否生效。

首先确保在 apiserver 中启用了 MutatingAdmissionWebhookValidatingAdmissionWebhook 这两个控制器,通过参数 --enable-admission-plugins 进行配置,当前 v1.22 版本已经内置默认开启了,如果没有开启则需要添加上这两个参数,然后重启 apiserver

然后通过运行下面的命令检查集群中是否启用了准入注册 API:

[root@master1 ~]#kubectl api-versions |grep admission
admissionregistration.k8s.io/v1

1.编写 webhook

满足了前面的先决条件后,接下来我们就来实现一个 webhook 示例,通过监听两个不同的 HTTP 端点(validate 和 mutate)来进行 validatingmutating webhook 验证。

这个 webhook 的完整代码可以在 Github 上获取:https://github.com/cnych/admission-webhook-example,该仓库 Fork 自项目 https://github.com/banzaicloud/admission-webhook-example。这个 webhook 是一个简单的带 TLS 认证的 HTTP 服务,用 Deployment 方式部署在我们的集群中。

我们通过输入https://github1s.com/cnych/admission-webhook-example1s快速在网页vscode里打开这个项目:

。。。。。shift,开发这一部分听得不是很懂啊。。。。。

[root@master1 ~]#kubectl explain ValidatingWebhookConfiguration.webhooks

代码中主要的逻辑在两个文件中:main.gowebhook.go,main.go 文件包含创建 HTTP 服务的代码,而webhook.go 包含 validates 和 mutates 两个 webhook 的逻辑,大部分代码都比较简单,首先查看 main.go 文件,查看如何使用标准 golang 包来启动 HTTP 服务,以及如何从命令行标志中读取 TLS 配置的证书

flag.StringVar(&parameters.certFile, "tlsCertFile", "/etc/webhook/certs/cert.pem", "File containing the x509 Certificate for HTTPS.")
flag.StringVar(&parameters.keyFile, "tlsKeyFile", "/etc/webhook/certs/key.pem", "File containing the x509 private key to --tlsCertFile.")

然后一个比较重要的是 serve 函数,用来处理传入的 mutate 和 validating 函数 的 HTTP 请求。该函数从请求中反序列化 AdmissionReview 对象,执行一些基本的内容校验,根据 URL 路径调用相应的 mutate 和 validate 函数,然后序列化 AdmissionReview 对象:

func (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {var body []byteif r.Body != nil {if data, err := ioutil.ReadAll(r.Body); err == nil {body = data}}if len(body) == 0 {glog.Error("empty body")http.Error(w, "empty body", http.StatusBadRequest)return}// 校验 Content-TypecontentType := r.Header.Get("Content-Type")if contentType != "application/json" {glog.Errorf("Content-Type=%s, expect application/json", contentType)http.Error(w, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)return}var admissionResponse *v1beta1.AdmissionResponsear := v1beta1.AdmissionReview{}if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {glog.Errorf("Can't decode body: %v", err)admissionResponse = &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}} else {if r.URL.Path == "/mutate" {admissionResponse = whsvr.mutate(&ar)} else if r.URL.Path == "/validate" {admissionResponse = whsvr.validate(&ar)}}admissionReview := v1beta1.AdmissionReview{}if admissionResponse != nil {admissionReview.Response = admissionResponseif ar.Request != nil {admissionReview.Response.UID = ar.Request.UID}}resp, err := json.Marshal(admissionReview)if err != nil {glog.Errorf("Can't encode response: %v", err)http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)}glog.Infof("Ready to write reponse ...")if _, err := w.Write(resp); err != nil {glog.Errorf("Can't write response: %v", err)http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)}
}

主要的准入逻辑是 validate 和 mutate 两个函数。validate 函数检查资源对象是否需要校验:不验证 kube-system 和 kube-public 两个命名空间中的资源,如果想要显示的声明不验证某个资源,可以通过在资源对象中添加一个 admission-webhook-example.qikqiak.com/validate=false 的 annotation 进行声明。如果需要验证,则根据资源类型的 kind,和标签与其对应项进行比较,将 service 或者 deployment 资源从请求中反序列化出来。如果缺少某些 label 标签,则响应中的 Allowed 会被设置为 false。如果验证失败,则会在响应中写入失败原因,最终用户在尝试创建资源时会收到失败的信息。validate 函数实现如下所示:

// validate deployments and services
func (whsvr *WebhookServer) validate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {req := ar.Requestvar (availableLabels                 map[string]stringobjectMeta                      *metav1.ObjectMetaresourceNamespace, resourceName string)glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v",req.Kind, req.Namespace, req.Name, resourceName, req.UID, req.Operation, req.UserInfo)switch req.Kind.Kind {case "Deployment":var deployment appsv1.Deploymentif err := json.Unmarshal(req.Object.Raw, &deployment); err != nil {glog.Errorf("Could not unmarshal raw object: %v", err)return &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}}resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMetaavailableLabels = deployment.Labelscase "Service":var service corev1.Serviceif err := json.Unmarshal(req.Object.Raw, &service); err != nil {glog.Errorf("Could not unmarshal raw object: %v", err)return &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}}resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMetaavailableLabels = service.Labels}if !validationRequired(ignoredNamespaces, objectMeta) {glog.Infof("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName)return &v1beta1.AdmissionResponse{Allowed: true,}}allowed := truevar result *metav1.Statusglog.Info("available labels:", availableLabels)glog.Info("required labels", requiredLabels)for _, rl := range requiredLabels {if _, ok := availableLabels[rl]; !ok {allowed = falseresult = &metav1.Status{Reason: "required labels are not set",}break}}return &v1beta1.AdmissionResponse{Allowed: allowed,Result:  result,}
}

判断是否需要进行校验的方法如下,可以通过 namespace 进行忽略,也可以通过 annotations 设置进行配置:

func validationRequired(ignoredList []string, metadata *metav1.ObjectMeta) bool {required := admissionRequired(ignoredList, admissionWebhookAnnotationValidateKey, metadata)glog.Infof("Validation policy for %v/%v: required:%v", metadata.Namespace, metadata.Name, required)return required
}func admissionRequired(ignoredList []string, admissionAnnotationKey string, metadata *metav1.ObjectMeta) bool {// skip special kubernetes system namespacesfor _, namespace := range ignoredList {if metadata.Namespace == namespace {glog.Infof("Skip validation for %v for it's in special namespace:%v", metadata.Name, metadata.Namespace)return false}}annotations := metadata.GetAnnotations()if annotations == nil {annotations = map[string]string{}}var required boolswitch strings.ToLower(annotations[admissionAnnotationKey]) {default:required = truecase "n", "no", "false", "off":required = false}return required
}

mutate 函数的代码是非常类似的,但不是仅仅比较标签并在响应中设置 Allowed,而是创建一个补丁,将缺失的标签添加到资源中,并将 not_available 设置为标签的值。

// main mutation process
func (whsvr *WebhookServer) mutate(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {req := ar.Requestvar (availableLabels, availableAnnotations map[string]stringobjectMeta                            *metav1.ObjectMetaresourceNamespace, resourceName       string)glog.Infof("AdmissionReview for Kind=%v, Namespace=%v Name=%v (%v) UID=%v patchOperation=%v UserInfo=%v",req.Kind, req.Namespace, req.Name, resourceName, req.UID, req.Operation, req.UserInfo)switch req.Kind.Kind {case "Deployment":var deployment appsv1.Deploymentif err := json.Unmarshal(req.Object.Raw, &deployment); err != nil {glog.Errorf("Could not unmarshal raw object: %v", err)return &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}}resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMetaavailableLabels = deployment.Labelscase "Service":var service corev1.Serviceif err := json.Unmarshal(req.Object.Raw, &service); err != nil {glog.Errorf("Could not unmarshal raw object: %v", err)return &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}}resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMetaavailableLabels = service.Labels}if !mutationRequired(ignoredNamespaces, objectMeta) {glog.Infof("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName)return &v1beta1.AdmissionResponse{Allowed: true,}}annotations := map[string]string{admissionWebhookAnnotationStatusKey: "mutated"}patchBytes, err := createPatch(availableAnnotations, annotations, availableLabels, addLabels)if err != nil {return &v1beta1.AdmissionResponse{Result: &metav1.Status{Message: err.Error(),},}}glog.Infof("AdmissionResponse: patch=%v\n", string(patchBytes))return &v1beta1.AdmissionResponse{Allowed: true,Patch:   patchBytes,PatchType: func() *v1beta1.PatchType {pt := v1beta1.PatchTypeJSONPatchreturn &pt}(),}
}

2.构建

其实我们已经将代码打包成一个 docker 镜像了,你可以直接使用,镜像仓库地址为:cnych/admission-webhook-example:v1。当然如果你希望更改部分代码,那就需要重新构建项目了,由于这个项目采用 go 语言开发,包管理工具更改为了 go mod,所以我们需要确保构建环境提前安装好 go 环境,当然 docker 也是必不可少的,因为我们需要的是打包成一个 docker 镜像。

获取项目:

mkdir admission-webhook && cd admission-webhook
git clone https://github.com/cnych/admission-webhook-example.git

我们可以看到代码根目录下面有一个 build 的脚本,只需要提供我们自己的 docker 镜像用户名然后直接构建即可:

export DOCKER_USER=cnych
./build

3.部署

为了部署 webhook server,我们需要在我们的 Kubernetes 集群中创建一个 service 和 deployment 资源对象,部署是非常简单的,只是需要配置服务的 TLS 配置。我们可以在代码根目录下面的 deployment 文件夹下面查看 deployment.yaml 文件中关于证书的配置声明,会发现从命令行参数中读取的证书和私钥文件是通过一个 secret 对象挂载进来的:

args:- -tlsCertFile=/etc/webhook/certs/cert.pem- -tlsKeyFile=/etc/webhook/certs/key.pem
[...]volumeMounts:- name: webhook-certsmountPath: /etc/webhook/certsreadOnly: true
volumes:
- name: webhook-certssecret:secretName: admission-webhook-example-certs

在生产环境中,对于 TLS 证书(特别是私钥)的处理是非常重要的,我们可以使用类似于 cert-manager 之类的工具来自动处理 TLS 证书,或者将私钥密钥存储在 Vault 中,而不是直接存在 secret 资源对象中。

我们可以使用任何类型的证书,但是需要注意的是我们这里设置的 CA 证书是需要让 apiserver 能够验证的,我们这里可以重用 Istio 项目中的生成的证书签名请求脚本。通过发送请求到 apiserver,获取认证信息,然后使用获得的结果来创建需要的 secret 对象。

首先,运行该脚本检查 secret 对象中是否有证书和私钥信息:

➜  ~ ./deployment/webhook-create-signed-cert.sh
creating certs in tmpdir /var/folders/x3/wjy_1z155pdf8jg_jgpmf6kc0000gn/T/tmp.IboFfX97
Generating RSA private key, 2048 bit long modulus (2 primes)
..................+++++
........+++++
e is 65537 (0x010001)
certificatesigningrequest.certificates.k8s.io/admission-webhook-example-svc.default created
NAME                                    AGE   REQUESTOR          CONDITION
admission-webhook-example-svc.default   1s    kubernetes-admin   Pending
certificatesigningrequest.certificates.k8s.io/admission-webhook-example-svc.default approved
secret/admission-webhook-example-certs created➜  ~ kubectl get secret admission-webhook-example-certs
NAME                              TYPE     DATA   AGE
admission-webhook-example-certs   Opaque   2      28s

一旦 secret 对象创建成功,我们就可以直接创建 deployment 和 service 对象。

➜  ~ kubectl apply -f deployment/rbac.yaml
➜  ~ kubectl apply -f deployment/deployment.yaml
deployment.apps "admission-webhook-example-deployment" created➜  ~ kubectl apply -f deployment/service.yaml
service "admission-webhook-example-svc" created

4、配置 webhook

现在我们的 webhook 服务运行起来了,它可以接收来自 apiserver 的请求。但是我们还需要在 kubernetes 上创建一些配置资源。首先来配置 validating 这个 webhook,查看 webhook 配置,我们会注意到它里面包含一个 CA_BUNDLE 的占位符:

clientConfig:service:name: admission-webhook-example-svcnamespace: defaultpath: "/validate"caBundle: ${CA_BUNDLE}

CA 证书应提供给 admission webhook 配置,这样 apiserver 才可以信任 webhook server 提供的 TLS 证书。因为我们上面已经使用 Kubernetes API 签署了证书,所以我们可以使用我们的 kubeconfig 中的 CA 证书来简化操作。代码仓库中也提供了一个小脚本用来替换 CA_BUNDLE 这个占位符,创建 validating webhook 之前运行该命令即可:

cat ./deployment/validatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh > ./deployment/validatingwebhook-ca-bundle.yaml

执行完成后可以查看 validatingwebhook-ca-bundle.yaml 文件中的 CA_BUNDLE 占位符的值是否已经被替换掉了。需要注意的是 clientConfig 里面的 path 路径是 /validate,因为我们代码在是将 validate 和 mutate 集成在一个服务中的。

然后就是需要配置一些 RBAC 规则,我们想在 deployment 或 service 创建时拦截 API 请求,所以 apiGroups 和apiVersions 对应的值分别为 apps/v1 对应 deployment,v1 对应 service。

webhook 的最后一部分是配置一个 namespaceSelector,我们可以为 webhook 工作的命名空间定义一个 selector,这个配置不是必须的,比如我们这里添加了下面的配置:

namespaceSelector:matchLabels:admission-webhook-example: enabled

则我们的 webhook 会只适用于设置了 admission-webhook-example=enabled 标签的 namespaces。

所以,首先需要在 default 这个 namespace 中添加该标签:

➜  ~ kubectl label namespace default admission-webhook-example=enabled
namespace "default" labeled

最后,创建这个 validating webhook 配置对象,这会动态地将 webhook 添加到 webhook 链上,所以一旦创建资源,就会拦截请求然后调用我们的 webhook 服务:

➜  ~ kubectl apply -f deployment/validatingwebhook-ca-bundle.yaml
validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" created

5、测试

现在让我们创建一个 deployment 资源来验证下是否有效,代码仓库下有一个 sleep.yaml 的资源清单文件,直接创建即可:

➜  ~ kubectl apply -f deployment/sleep.yaml
Error from server (required labels are not set): error when creating "deployment/sleep.yaml": admission webhook "required-labels.qikqiak.com" denied the request: required labels are not set

正常情况下创建的时候会出现上面的错误信息,然后部署另外一个 sleep-with-labels.yaml 的资源清单:

➜  ~ kubectl apply -f deployment/sleep-with-labels.yaml
deployment.apps "sleep" created

可以看到可以正常部署,然后我们将上面的 deployment 删除,然后部署另外一个 sleep-no-validation.yaml 资源清单,该清单中不存在所需的标签,但是配置了 admission-webhook-example.qikqiak.com/validate=false 这样的 annotation,所以正常也是可以正常创建的:

➜  ~ kubectl delete deployment sleep
➜  ~ kubectl apply -f deployment/sleep-no-validation.yaml
deployment.apps "sleep" created

6、部署 mutating webhook

首先,我们将上面的 validating webhook 删除,防止对 mutating 产生干扰,然后部署新的配置。 mutating webhookvalidating webhook 配置基本相同,但是 webook server 的路径是 /mutate,同样的我们也需要先填充上 CA_BUNDLE 这个占位符。

➜  ~ kubectl delete validatingwebhookconfiguration validation-webhook-example-cfg
validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" deleted➜  ~ cat ./deployment/mutatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh > ./deployment/mutatingwebhook-ca-bundle.yaml➜  ~ kubectl apply -f deployment/mutatingwebhook-ca-bundle.yaml
mutatingwebhookconfiguration.admissionregistration.k8s.io "mutating-webhook-example-cfg" created

现在我们可以再次部署上面的 sleep 应用程序,然后查看是否正确添加 label 标签:

➜  ~ kubectl apply -f deployment/sleep.yaml
deployment.apps "sleep" created➜  ~ kubectl get deploy sleep -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:annotations:admission-webhook-example.qikqiak.com/status: mutateddeployment.kubernetes.io/revision: "1"creationTimestamp: "2020-06-01T08:10:04Z"generation: 1labels:app.kubernetes.io/component: not_availableapp.kubernetes.io/instance: not_availableapp.kubernetes.io/managed-by: not_availableapp.kubernetes.io/name: not_availableapp.kubernetes.io/part-of: not_availableapp.kubernetes.io/version: not_availablename: sleepnamespace: default
...

最后,我们重新创建 validating webhook,来一起测试。现在,尝试再次创建 sleep 应用。正常是可以创建成功的,我们可以查看下 admission-controllers 的文档。

准入控制分两个阶段进行,第一阶段,运行 mutating admission 控制器,第二阶段运行 validating admission 控制器。

所以 mutating webhook 在第一阶段添加上缺失的 labels 标签,然后 validating webhook 在第二阶段就不会拒绝这个 deployment 了,因为标签已经存在了,用 not_available 设置他们的值。

➜  ~ kubectl apply -f deployment/validatingwebhook-ca-bundle.yaml
validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" created➜  ~ kubectl apply -f deployment/sleep.yaml
deployment.apps "sleep" created

⚠️但是如果我们有这样的相关需求就单独去开发一个准入控制器的 webhook 是不是就显得非常麻烦,不够灵活了,为了解决这个问题我们可以使用 Kubernetes 提供的一些策略管理引擎,在不需要编写代码的情况也可以来实现我们的这些需求,比如 Kyverno(这个可能会好用些)Gatekeeper(这个可能会复杂些) 等等,后续我们再进行详细讲解。

关于我

我的博客主旨:我希望每一个人拿着我的博客都可以做出实验现象,先把实验做出来,然后再结合理论知识更深层次去理解技术点,这样学习起来才有乐趣和动力。并且,我的博客内容步骤是很完整的,也分享源码和实验用到的软件,希望能和大家一起共同进步!

各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人免费帮您解决问题:

  1. 个人微信二维码:x2675263825 (舍得), qq:2675263825。

  2. 个人博客地址:www.onlyonexl.cn

  3. 个人微信公众号:云原生架构师实战

  4. 个人csdn

    https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421

最后

​ 好了,关于Admission实验就到这里了,感谢大家阅读,最后贴上我女神的photo,祝大家生活快乐,每天都过的有意义哦,我们下期见!

Admission(准入控制器)-2021.12.05相关推荐

  1. 1.4编程基础之逻辑表达式与条件分支 09判断能否被3,5,7整除(2021.12.05)

    题目链接 09 判断能否被3,5,7整除 总时间限制: 1000ms 内存限制: 65536kB 描述 给定一个整数,判断它能否被3,5,7整除,并输出以下信息: 1.能同时被3,5,7整除(直接输出 ...

  2. Crd(自定义资源类型)2021.12.05

    目录 文章目录 目录 实验环境 实验软件 1.什么是CRD 2.CRD的定义 3.Controller 4.Operator 5.参考文档 关于我 最后 实验环境 实验环境: 1.win10,vmwr ...

  3. 《安富莱嵌入式周报》第246期:2021.12.27--2022.01.02

    往期周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - P ...

  4. 2021年05月软件设计师真题透析

    2021年05月软件设计师上午真题及答案解析 1. 在 CPU 中,用( )给出将要执行的下一条指令在内存中的地址. A.程序计数器 B.指令寄存器 C.主存地址寄存器 D.状态条件寄存器 答案:A, ...

  5. webapi控制器怎么接收json_新手指南之 Kubernetes 准入控制器

    Kubernetes 准入控制器在安全性方面具有明显优势.为了增进各位读者对它的了解,今天 K8sMeetup 中国社区翻译了工程师 Malte Isberner 的技术博客,以两个生动的演示和相关代 ...

  6. 《安富莱嵌入式周报》第245期:2021.12.20--2021.12.26

    往期周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - P ...

  7. 【云原生 | Kubernetes 系列】K8s 实战 一文学会如何从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器

    PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器 前言 一.Pod 安全性 二.访问权限 三.标准化 PodSecurityPolicy 3.1.去掉纯粹变更性质的 ...

  8. 云原生之Kubernetes:18、详解准入控制器

    文章目录 前言 1.什么是准入控制器? 2.为什么我们需要它? 3.常用的准入控制器 4.如何开启准入控制器 5.如何关闭准入控制器 6.实战:控制器的使用 前言 Admission Controll ...

  9. 为什么需要 Kubernetes 准入控制器

    Kubernetes 准入控制器是集群管理必要功能.这些控制器主要在后台工作,并且许多可以作为编译插件使用,它可以极大地提高部署的安全性. 准入控制器在 API 请求传递到 APIServer 之前拦 ...

最新文章

  1. kafka消费者命令行的使用方法
  2. 服务器定期巡检项目,服务器定期巡检制度..docx
  3. 最新增值税商品税目编码表_大家好!我叫增值税!这是我的最新最全税率表
  4. matlab中sum函数
  5. 第一章: 新的结构化元素
  6. 错误记录(一)Could not get constructor for org.hibernate.persister.entity.SingleTableEntityPersister报错解决办法
  7. python计算坐标点欧式距离_计算机视觉课堂笔记-4
  8. 【Chrome小技巧】Chrome浏览器如何实现下载速度加快?
  9. 传统企业连接互联网的五种方式
  10. 申请QQ认证空间标志领黄钻90天 明星粉丝团认证空间
  11. 明白这30条人生道理,你就超过了80%的男人
  12. 常用 25/26 Flash 系列器件型号、ID、容量对照表
  13. Error: Rule can only have one resource source (provided resource and test + include + exclude)...
  14. boost - 智能指针介绍
  15. 【C语言编程】求Fibonacci(斐波那契)数列前40个数
  16. 一、Tomcat 配置
  17. 技术实现之http请求封装
  18. 避 孕药熟知的几种负面影响
  19. python字典查找元素_详解Python字典小结
  20. java 中 ajax 的学习

热门文章

  1. 3.3.1 TextView(文本框)
  2. SPA 文件结构记录
  3. 微信支付一 :公众号支付1
  4. 我在北京每月白捡一万元(转贴)
  5. python判断座位号_利用Python实现图书馆座位抓取(自动预约),python,抢座
  6. java.sql.SQLException: The server time zone value‘xxxxxxxx‘ is unrecognized
  7. 打开桌面计算机投屏到扩展屏,电脑扩展显示器投屏方案
  8. 数学模型(第五版) PDF 习题参考解答 第5版
  9. java 对es的操作
  10. python使用代理爬虫_python使用代理爬虫导致无法访问网页