Kubenetes-CKA-和-CKAD-认证备考指南-二-

91 阅读24分钟

Kubenetes CKA 和 CKAD 认证备考指南(二)

原文:Kubernetes: Preparing for the CKA and CKAD Certifications

协议:CC BY-NC-SA 4.0

七、扩展应用

我们已经在ReplicaSetDeployment的规范中看到了一个replicas场。此字段指示应运行多少个 Pod 副本。

手动缩放

在声明形式中,可以编辑部署的规范来更改此字段的值:

apiVersion: apps/v1 kind:
Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 4
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image:  nginx
        name: nginx

在命令形式中,命令kubectl scale用于改变该值:

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl scale deployment nginx --replicas=4
deployment.apps/nginx scaled

自动缩放

HorizontalPodAutoscaler资源(通常称为 HPA )可用于根据当前副本的 CPU 使用情况自动扩展部署。

HPA取决于集群上 Kubernetes 度量服务器 1 的安装。

要安装它,请运行:

$ kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.7/components.yaml

您必须编辑metrics-server部署,以便向容器中启动的命令添加--kubelet-insecure-tls--kubelet-preferred-address-types=InternalIP标志,并添加hostNetwork: true:

$ kubectl edit deployment metrics-server -n kube-system

    spec:
      hostNetwork: true ## add this line
      containers:
      - args:
        - --cert-dir=/tmp
        - --secure-port=4443
        - --kubelet-insecure-tls # add this line
        - --kubelet-preferred-address-types=InternalIP ## add this line

您可以使用以下命令检查v1beta1.metrics.k8s.io APIService 的状态:

$ kubectl get apiservice v1beta1.metrics.k8s.io -o yaml
[...]
status:
  conditions:
  - lastTransitionTime: "2020-08-15T15:38:44Z"
    message: all checks passed
    reason: Passed
    status: "True"
    type: Available

现在,您应该可以从以下命令中获得结果:

$ kubectl top nodes
NAME         CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
Controller   95m          9%     1256Mi              34%
worker-0     34m          3%     902Mi               25%
worker-1     30m          3%     964Mi               26%

现在,您可以使用单个副本开始部署。请注意,CPU 资源请求是 HPA 工作的必要条件:

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl set resources --requests=cpu=0.05 deployment/nginx
deployment.extensions/nginx resource requirements updated

现在为该部署创建一个HorizontalPodAutoscaler资源,该资源能够以强制形式将部署从一个副本自动扩展到四个副本,CPU 利用率为 5%:

$ kubectl autoscale deployment nginx \
--min=1 --max=4 --cpu-percent=5
horizontalpodautoscaler.autoscaling/nginx autoscaled

或者以声明的形式:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx
spec:
  minReplicas: 1
  maxReplicas: 4
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx
  targetCPUUtilizationPercentage: 5

为了提高当前运行的 Pod 的 CPU 利用率,您可以使用curl命令对它发出大量请求:

$ kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-xxxxxxxxxx-yyyyy   1/1     Running   0          31s

$ kubectl port-forward pod/nginx-xxxxxxxxxx-yyyyy 8084:80
Forwarding from 127.0.0.1:8084 -> 80

而在另一个终端中:

$ while : ; do curl http://localhost:8084; done

同时,您可以使用以下命令跟踪 Pod 的 CPU 利用率:

$ kubectl top pods
NAME                     CPU(cores)   MEMORY(bytes)
nginx-xxxxxxxxxx-yyyyy   3m           2Mi

一旦 CPU 利用率超过 5%,将自动部署第二个单元:

$ kubectl get hpa nginx
NAME    REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS
nginx   Deployment/nginx   7%/5%     1         4         2

$ kubectl get pods
NAME                     READY   STATUS   RESTARTS   AGE
nginx-5c55b4d6c8-fgnlz   1/1     Running  0          12m
nginx-5c55b4d6c8-hzgfl   1/1     Running  0          81s

如果您停止curl请求并观察创建的 HPA,您可以看到,在 CPU 利用率再次降低 5 分钟后,复制副本的数量将再次设置为 1:

$ kubectl get hpa nginx -w
NAME    REFERENCE          TARGETS   MINPODS   MAXPODS   REPLICAS AGE
nginx   Deployment/nginx   4%/5%     1         4         2        10m
nginx   Deployment/nginx   3%/5%     1         4         2        10m
nginx   Deployment/nginx   0%/5%     1         4         2        11m
nginx   Deployment/nginx   0%/5%     1         4         1        16m

您可以检查 HPA 创建的事件:

$ kubectl describe hpa nginx
[...]
Events:
  Type   Reason            Age From                      Message
  ----   ------            --- ----                      -------
  Normal SuccessfulRescale 10m horizontal-pod-autoscaler New size: 2; reason: \
cpu resource utilization (percentage of request) above target
  Normal SuccessfulRescale 2m47s horizontal-pod-autoscaler New size: 1; reason: \
All metrics below target

Footnotes 1

https://github.com/kubernetes-sigs/metrics-server

 

八、应用自修复

当您在群集上启动一个 Pod 时,它被安排在群集的特定节点上。如果节点在给定时刻无法继续托管该 Pod,则该 Pod 将不会在新节点上重新启动—应用不会自修复。

让我们尝试一下,在一个有多个工作者的集群上(例如,在第一章中安装的集群上)。

首先,运行一个 Pod 然后检查它在哪个节点上被调度:

$ kubectl run nginx --image=nginx
pod/nginx created
$ kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE
nginx   1/1     Running   0          12s   10.244.1.8   worker-0

这里,Pod 已经被安排在节点worker-0上。

让我们将此节点置于维护模式,看看 Pod 会发生什么情况:

$ kubectl drain worker-0 --force
node/worker-0 cordoned
WARNING: deleting Pods not managed by ReplicationController, ReplicaSet, Job, Daemon\
Set or StatefulSet: default/nginx
evicting pod "nginx"
pod/nginx evicted
node/worker-0 evicted
$ kubectl get pods
No resources found in default namespace.

您可以看到您创建的 Pod 已经消失,并且没有在另一个节点中重新创建。我们在这里完成实验。您可以使您的节点再次可调度:

$ kubectl uncordon worker-0
node/worker-0 uncordoned

控制员呼叫救援

我们在第五章“Pod 控制器”一节中已经看到,如果一个节点停止工作,使用 Pod 控制器可以确保您的 Pod 被调度到另一个节点。

让我们再体验一次,用一个Deployment:

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl get pods -o wide
NAME                     READY   STATUS   RESTARTS   AGE   IP          NODE
nginx-554b9c67f9-ndtsz   1/1     Running  0          11s   10.244.1.9 worker-0
$ kubectl drain worker-0
node/worker-0 cordoned
evicting pod "nginx-554b9c67f9-ndtsz"
pod/nginx-554b9c67f9-ndtsz evicted
node/worker-0 evicted
$ kubectl get pods -o wide
NAME                     READY   STATUS   RESTARTS   AGE      IP            NODE
nginx-554b9c67f9-5kz5v   1/1     Running  0          4s      10.244.2.9    worker-1

这一次,我们可以看到一个 Pod 已在集群的另一个节点中重新创建——我们的应用现在在节点驱逐后仍然存在。

活性探针

可以为 Pod 的每个容器定义一个活性探测器。如果 kubelet 不能成功地执行给定次数的探测,则认为容器不健康,并在同一个 Pod 中重新启动。

该探针应用于检测容器是否无响应。

活性探测有三种可能性:

  • 发出一个 HTTP 请求。

    如果您的容器是一个 HTTP 服务器,您可以添加一个总是以成功响应进行回复的端点,并使用该端点定义探测。如果您的后端不再健康,这个端点可能也不会响应。

  • 执行命令。

    大多数服务器应用都有相关的 CLI 应用。您可以使用这个 CLI 在服务器上执行一个非常简单的操作。如果服务器不健康,很可能它也不会响应这个简单的请求。

  • 建立 TCP 连接。

    当运行在容器中的服务器通过非 HTTP 协议(在 TCP 之上)进行通信时,您可以尝试打开应用的套接字。如果服务器不正常,它可能不会响应此连接请求。

您必须使用声明形式来声明活跃度探测器。

关于就绪性探测的说明

注意,也可以为容器定义一个就绪探测器。就绪探测器的主要作用是指示 Pod 是否准备好为网络请求提供服务。当就绪探测成功时,Pod 将被添加到匹配Services的后端列表中。

稍后,在容器执行期间,如果准备就绪探测失败,Pod 将从Services的后端列表中删除。这对于检测容器不能处理更多的连接(例如,如果它已经在处理大量的连接)并停止发送新的连接是有用的。

HTTP 请求活性探测

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /healthz

这里,我们定义了一个查询/healthz端点的探测器。由于 nginx 在默认情况下没有被配置为回复这个路径,它将回复一个 404 响应代码,探测将会失败。这不是一个真实的案例,但是它模拟了一个 nginx 服务器对一个简单请求的错误回复。

您可以在 Pod 事件中看到,在三次失败的探测之后,容器被重新启动:

$ kubectl describe pod nginx
[...]
Events:
  Type     Reason     Age                From              Message
  ----     ------     ----               ----              -------
  Normal   Started    31s                kubelet, minikube Started container nginx
  Normal   Pulling    0s (x2 over 33s)   kubelet, minikube Pulling image "nginx"
  Warning  Unhealthy  0s (x3 over 20s)   kubelet, minikube Liveness probe failed: HTTP probe failed with statuscode: 404
  Normal   Killing    0s                 kubelet, minikube Container nginx failed liveness probe, will be restarted

命令活性探测

apiVersion: v1

kind: Pod
metadata:
  name: postgres
spec:
  containers:
  - image: postgres
    name: postgres
    livenessProbe:
      initialDelaySeconds: 10
      exec:
        command:
        - "psql"
        - "-h"
        - "localhost"
        - "-U"
        - "unknownUser"
        - "-c"
        - "select 1"

这里,活跃度探测器试图使用psql命令连接到服务器,并以用户unknownUser的身份执行一个非常简单的 SQL 查询(SELECT 1)。由于该用户不存在,查询将失败。

您可以在 Pod 事件中看到,在三次失败的探测之后,容器被重新启动:

$ kubectl describe pod postgres
[...]
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  <unknown>                default-scheduler  Successfully assigned default/postgres to minikube
  Warning  Unhealthy  0s (x3 over 20s)         kubelet, minikube  Liveness probe failed: psql: error: could not connect to server: FATAL: role "unknownUser" does not exist
  Normal   Killing    0s                       kubelet, minikube  Container postgres failed liveness probe, will be restarted

TCP 连接活性探测

apiVersion: v1

kind: Pod
metadata:
  name: postgres
spec:
  containers:
  - image: postgres
    name: postgres
    livenessProbe:
      initialDelaySeconds: 10
      tcpSocket:
        port: 5433

这里,活跃度探测器试图连接到5433端口上的容器。由于postgres监听端口5432,连接将失败。

您可以在 Pod 事件中看到,在三次失败的探测之后,容器被重新启动:

$ kubectl describe pod postgres
[...]
Events:
  Type     Reason     Age               From                Message
  ----     ------     ----              ----                -------
  Normal   Started    25s               kubelet, minikube   Started container postgres
  Warning  Unhealthy  0s (x3 over 15s)  kubelet, minikube   Liveness probe failed: dial tcp 172.17.0.3:5433: connect: connection refused
  Normal   Killing    0s                kubelet, minikube   Container postgres failed  liveness probe, will be restarted

资源限制和服务质量(QoS)等级

您可以为每个 Pods 容器定义资源(CPU 和内存)请求和限制。

资源请求值用于在至少具有所请求的可用资源的节点中调度 Pod(参见第九章“资源请求”一节)。

如果不声明限制,每个容器仍然可以访问节点的所有资源;在这种情况下,如果一些容器在给定的时间没有使用所有它们请求的资源,一些其他容器将能够使用它们,反之亦然。

相反,如果为容器声明了限制,容器将被约束到那些特定的资源。如果它试图分配比它的限制更多的内存,它将得到一个内存分配错误,可能会崩溃或工作在降级模式;并且它只能访问其限制范围内的 CPU。

根据是否声明了请求和限制值,为 Pod 保证了不同的服务质量:

  • 如果一个 Pod 的所有容器都声明了对所有资源(CPU 和内存)的请求和限制,并且限制等于请求,则该 Pod 将以保证的 QoS 等级运行。

  • 或者如果 Pod 的至少一个容器具有资源请求或限制,则 Pod 将以可突发的 QoS 等级运行。

  • 否则,如果没有为其容器声明请求或限制,则该 Pod 将以尽力而为 QoS 等级运行。

如果一个节点用完了不可压缩的资源(内存),相关联的 kubelet 可以决定弹出一个或多个 pod,以防止资源完全匮乏。

被驱逐的 pod 取决于它们的服务质量等级:首先是尽力而为的 pod,然后是突发的 pod,最后是保证的 pod。

九、调度 POD

当您希望将一个 Pod 运行到 Kubernetes 集群中时,通常不需要指定希望 Pod 在哪个节点上运行。这是 Kubernetes 调度程序的工作,决定它将在哪个节点上运行。

Pod 规格包含一个nodeName字段,指示在哪个节点上调度 Pod。

调度程序永远在监视 POD;当它发现一个具有空的nodeName字段的 Pod 时,调度器确定在其上调度该 Pod 的最佳节点,然后修改 Pod 规范以用所选节点写入nodeName字段。

并行地,受特定节点影响的 kubelet 组件监视 Pods 当一个标有nodeName的 Pod 与一个 kubelet 的节点相匹配时,该 Pod 会被影响到 kubelet,kube let 会将它部署到它的节点。

使用标签选择器在特定节点上调度 pod

Pod 规范包含一个nodeSelector字段,作为键值对的映射。设置后,Pod 仅可部署在将每个键值对作为标签的节点上。

典型的用法是节点有一些标签来指示一些特性,当您想要在具有特性的节点上部署一个 Pod 时,您可以将相应的标签添加到 Pod 的nodeSelector字段。

向节点添加标签

第一步是向节点添加标签。假设您有四个节点,两个使用 SSD 磁盘,两个使用 HDD 磁盘。您可以使用以下命令标记节点:

$ kubectl label node worker-0 disk=ssd
node/worker-0 labeled
$ kubectl label node worker-1 disk=ssd
node/worker-1 labeled
$ kubectl label node worker-2 disk=hdd
node/worker-2 labeled
$ kubectl label node worker-3 disk=hdd
node/worker-3 labeled

在这些节点中,有两个提供 GPU 单元。让我们给它们贴上标签:

$ kubectl label node worker-0 compute=gpu
node/worker-0 labeled
$ kubectl label node worker-2 compute=gpu
node/worker-1 labeled

向窗格添加节点选择器

假设您想要部署一个需要 SSD 磁盘的 Pod。您可以创建此部署。Pod 可安排在worker-0worker-1进行:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      nodeSelector:
        disk: ssd
      containers:
      - image: nginx
        name: nginx

现在,如果 Pod 需要一个 SSD 磁盘一个 GPU 单元,您可以创建这个部署。Pod 仅可在worker-0安排:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      nodeSelector:
        disk: ssd
        compute: gpu
      containers:
      - image: nginx
        name: nginx

人工调度

请记住,Kubernetes 调度程序查找带有空nodeName的 Pods,kubelet 组件查找带有相关节点名称的 Pods。

如果您创建了一个 Pod,并在它的规范中为自己指定了nodeName,那么调度程序将永远看不到它,相关联的 kubelet 将立即采用它。其效果是,Pod 将在指定的节点上被调度,而无需调度程序的帮助。

DaemonSet

DaemonSet Kubernetes 资源保证所有(或给定子集的)节点运行给定 Pod 的副本。

DaemonSet 的典型用途是在集群的每个节点上部署守护程序(存储守护程序、日志守护程序、监视守护程序)。

由于这种特殊性,由 DaemonSet 创建的 Pods 不是由 Kubernetes 调度程序调度的,而是由 daemon set 控制器本身调度的。

DaemonSet 的规范类似于部署规范,但有以下区别:

  • DaemonSet 规范不包含replicas字段,因为该数量由所选节点的数量给出。

  • 部署的strategy字段被 DaemonSet 中的updateStrategy字段替换。

  • DaemonSet 规范中没有progressDeadlineSecondspaused字段。

默认情况下,DaemonSet 将在群集的每个节点上部署 pod。如果您只想选择节点的子集,您可以使用 Pod 规范的nodeSelector字段按标签选择节点,就像您对部署所做的那样(参见第九章“使用标签选择器在特定节点上调度 Pod”一节)。

例如,下面是一个 DaemonSet,它将在标有compute=gpu的节点上部署一个假想的 GPU 守护进程:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: gpu-daemon
  labels:
    app: gpu-daemon
spec:
  selector:
    matchLabels:
      app: gpu-daemon
  template:
    metadata:
      labels:
        app: gpu-daemon
    spec:
      nodeSelector:
        compute: gpu
      containers:
      - image: gpu-daemon
        name: gpu-daemon

静态吊舱

静态 pod 直接连接到 kubelet 守护进程。它们在位于运行 kubelet 守护进程的节点的主机上的特定目录中的文件中声明。

您可以在 kubelet 配置文件中的staticPodPath字段下找到该目录。

kubelet 的配置可以通过 Kubernetes API 访问,路径如下:/api/v1/nodes/<node-name>/proxy/configz

要轻松访问 API,您可以运行kubectl proxy命令:

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

您现在可以访问http://127.0.0.1:8001上的 API,拥有与kubectl相同的权限。

现在,在另一个终端中,您可以执行curl命令来获取worker-0节点上 kubelet 守护进程的配置:

$ curl "http://localhost:8001/api/v1/nodes/worker-0/proxy/configz" \
  | python -m json.tool
{
    "kubeletconfig":  {
        "staticPodPath": "/etc/kubernetes/manifests",
[...]
    }
}

您现在可以创建一个清单来声明这个目录上的 Pod,在worker-0主机上:

$ gcloud compute ssh worker-0
Welcome to Ubuntu 18.04.3 LTS
$ cat <<EOF | sudo tee /etc/kubernetes/manifests/nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
EOF

回到您的开发人员机器,您可以看到 Pod 出现在正在运行的 Pod 的列表中,kubelet 在 Pod 的名称后面加上了节点的名称:

$ kubectl get pods
NAME             READY   STATUS   RESTARTS   AGE
nginx-worker-0   1/1     Running  0          11s

如果您删除 Pod,kubelet 会立即重新创建它:

$ kubectl delete pod nginx-worker-0
pod "nginx-worker-0" deleted
$ kubectl get pods
NAME             READY   STATUS   RESTARTS   AGE
nginx-worker-0   0/1     Pending  0          1s

当您从worker-0主机上删除清单时,kubelet 会立即删除该 Pod。

$ gcloud compute ssh worker-0
Welcome to Ubuntu 18.04.3 LTS

$ sudo rm /etc/kubernetes/manifests/nginx.yaml

资源请求

每个节点都有最大的 CPU 和内存容量。每次在节点上安排一个 Pod 时,都会从该节点上可用的 CPU 和内存中删除该 Pod 请求的 CPU 和内存量。

如果某个节点上的可用资源少于该 Pod 请求的数量,则无法在该节点上计划该 Pod。

因此,为您部署的所有pod 声明资源请求非常重要。否则,可用资源的计算将是不准确的。

以命令的形式

kubectl set resources命令用于设置对象上的资源请求(这里是部署):

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl set resources deployment nginx \
  --requests=cpu=0.1,memory=1Gi
deployment.extensions/nginx resource requirements updated

以声明的形式

您可以使用容器规范的resources字段为 Pod 的每个容器声明资源请求:

apiVersion: apps/v1

kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources:
          requests:
            cpu: 100m
            memory: 1Gi

运行多个调度程序

Kubernetes 附带了一个默认的调度程序,并允许您并行运行自己的调度程序。在这种情况下,当您创建一个 Pod 时,您将能够选择希望用于此 Pod 的调度程序。

您可以在 feloy/scheduler-round-robin GitHub 存储库中获得一个示例调度程序。 1

这个调度程序的代码非常简单,不能用于生产,但是演示了调度程序的生命周期:

  • 监听没有nodeName值的 pod

  • 选择节点

  • 将所选节点绑定到窗格

  • 发送事件

一旦这个新的调度程序被部署到您的集群中(请遵循存储库中的说明),您就可以验证调度程序已经找到了集群的工作线程:

$ kubectl logs scheduler-round-robin-xxxxxxxxxx-yyyyy -f
found 2 nodes: [worker-0 worker-1]

现在,您可以创建指定此特定调度程序的部署:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 4
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      schedulerName: scheduler-round-robin
      containers:
      - image: nginx
        name: nginx

使用命令kubectl get pods -o wide,您可以看到两个吊舱已经部署在worker-0和两个worker-1

检查调度程序事件

如果您查看附加到已部署的 Pod 的事件,您可以看到该 Pod 已经由scheduler-round-robin调度程序进行了调度:

$ kubectl describe pod nginx-xxxxxxxxxx-yyyyy
[...]
Events:
  Type    Reason     Age   From                    Message
  ----    ------     ----  ----                    -------
  Normal  Scheduled  30s   scheduler-round-robin   pod nginx-6dcb7cd47-9c9w5 schedule\
d to node worker-0

您可以浏览所有事件以查找由调度程序创建的事件:

$ kubectl get events | grep Scheduled
0s          Normal    Scheduled                 pod/nginx-554b9c67f9-snpkb      \
          Successfully assigned default/nginx-554b9c67f9-snpkb to worker-0

Footnotes 1

https://github.com/feloy/scheduler-round-robin

 

十、发现和负载均衡

当您部署一个 Pod 时,它不容易接近。如果定义一个包含多个容器的 Pod,这些容器将可以通过 localhost 接口进行通信,但是如果不知道另一个 Pod 的 IP 地址,Pod 的容器将无法与另一个 Pod 的容器进行通信。

但是 Pods 是不稳定的。我们已经看到,Pod 本身是不可用的,因为它可以在任何时候被逐出节点,并且不会自动重新创建。像ReplicaSet这样的控制器对于保证给定数量的 Pod 副本运行是必要的。在这种情况下,即使获得了一个 Pod 的 IP 地址,也不能保证这个 Pod 存活下来,下一个也不会有第一个的 IP 地址。

服务

Kubernetes 资源用于以可复制的方式通过网络访问 Pod。

在命令形式中,您可以使用kubectl expose命令:

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl expose deployment nginx --port 80
service/webapp exposed
$ kubectl get services
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
webapp       ClusterIP   10.97.68.130   <none>        80/TCP    5s

在声明形式中,您可以使用以下模板创建服务:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - port: 80
  selector:
    app: nginx

现在,我们可以尝试部署另一个 Pod,连接到它,并尝试与这个nginx Pod 通信:

$ kubectl run \
  --image=busybox box \
   sh -- -c 'sleep $((10**10))'
pod/box created
$ kubectl exec -it box sh
/ # wget http://webapp -q -O -
<!DOCTYPE html>
<html> [...]
</html>
/ # exit

选择器

在前面的例子中,您已经看到了服务使 Pod 可访问。更一般地说,服务是一系列后端pod 的前端。该后端 pod 列表由Service资源的selector字段决定;所有带有匹配该选择器的键和值作为标签的窗格都有资格成为后端列表的一部分。

就绪探测

一旦由selector字段确定了有资格成为服务后端的 pod 列表,pod 的就绪性也被考虑在内。当 Pod 处于就绪状态时,Pod 被有效地插入后端列表中。

当 Pod 的所有容器都准备好时,它被认为是准备好了,当它没有定义一个readinessProbe或当它的readinessProbe成功时,容器也准备好了。

请注意,在 Pod 启动时会考虑这种就绪性,但在 Pod 的整个生命周期中也会考虑这种就绪性。在任何时候,容器可以声明自己没有准备好(例如,因为它认为它不能处理更多的请求),并且该 Pod 将立即从匹配服务的后端列表中删除。

端点

Service的有效后端由Endpoints资源来实现。端点控制器负责创建和删除端点,这取决于 pod 和服务选择器的准备情况。

您可以使用命令查看服务的端点列表(这里是nginx)

$ kubectl get endpoints nginx
NAME    ENDPOINTS                         AGE
nginx   172.17.0.10:80,172.17.0.9:80 7m   48s

在这种情况下,服务有两个端点,服务的流量将被路由到这两个端点。

你可以得到两个端点的细节。在这里,你可以看到两个端点是两个吊舱nginx-86c57db685-g4fqr and nginx-86c57db685-9hp58:

$ kubectl get endpoints nginx -o yaml
apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app: nginx
  name: nginx
  [...]
subsets:
- addresses:
  - ip: 172.17.0.10
    nodeName: minikube
    targetRef:
      kind: Pod
      name: nginx-86c57db685-g4fqr
      namespace: default
      resourceVersion: "621228"
      uid: adb2d120-14ed-49ad-b9a5-4389412b73b1
  - ip: 172.17.0.9
    nodeName: minikube
    targetRef:
      kind: Pod
      name: nginx-86c57db685-9hp58
      namespace: default
      resourceVersion: "621169"
      uid: 5b051d79-9951-4ca9-a436-5dbe3f46169b
ports:
- port: 80
  protocol: TCP

服务类型

ClusterIP(群集 IP)

默认情况下,服务是用ClusterIP类型创建的。使用这种类型,只能从集群内部访问服务。

将为该服务保留一个本地 IP 地址,并且将创建一个指向该地址的 DNS 条目,在我们的示例中是以<name>.<namespace>.svc.cluster.local的形式。

如果您检查容器中的resolv.conf文件,您会看到search条目表明:

$ kubectl exec -it box cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local

得益于此,从一个 Pod 内部,您可以访问在名称空间中定义的服务,只使用它的名称(这里是webapp)或它的 name.namespace(这里是webapp.default)或它的 name.namespace.svc(这里是webapp.default.svc)或它的完整名称(webapp.default.svc.cluster.local)。

要访问在另一个名称空间中定义的服务,您必须至少指定名称和名称空间(例如,another-app.other-namespace)。

节点端口

如果想从集群外部访问服务,可以使用NodePort类型。除了创建 ClusterIP 之外,这将在集群的每个节点上分配一个端口(默认情况下在 30000–32767 范围内),该端口将路由到 ClusterIP。

LoadBalancer(负载均衡器)

如果想从云环境外部访问服务,可以使用LoadBalancer类型。除了创建一个NodePort之外,它还会创建一个外部负载均衡器(如果你使用一个托管的 Kubernetes 集群,比如谷歌 GKE、Azure AKS、亚马逊 EKS 等。),它通过NodePort路由到集群 IP。

外部名

这是一种特定类型的服务,其中不使用selector字段,而是使用 DNS CNAME记录重定向到外部 DNS 名称。

进入

使用负载均衡器服务,您可以访问应用的微服务。如果您有几个应用,每个应用都有几个访问点(至少前端和 API),您将需要保留大量负载均衡器,这可能非常昂贵。

一个Ingress相当于一个 Apache 或者 nginx 虚拟主机;它允许将多个微服务的访问点复用到单个负载均衡器中。对请求的主机名和路径进行选择。

为了使用Ingress资源,你必须在集群中安装一个入口控制器

安装 nginx 入口控制器

您可以遵循安装说明。 1 总之,你必须执行

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/
provider/baremetal/deploy.yaml
[...]

获取入口控制器监听外部连接的端口,在本例中是端口 32351 (映射到端口 80)和 31296 (映射到 443),并将它们存储在环境变量中以备后用:

$ kubectl get services -n ingress-nginx
NAME           TYPE      CLUSTER-IP      EXTERNAL-IP    PORT(S)  \
   AGE
ingress-nginx  NodePort  10.100.169.243  <none>     80:32351/TCP,443:31296/TCP\
   9h
$ HTTP_PORT=32351
$ HTTPS_PORT=31296

请注意,ingress-nginx服务的类型是NodePort;该服务可以在集群的每个工作节点上通过端口 32351(用于 HTTP 连接)和 31296(用于 HTTPS 连接)进行访问。

我们必须添加防火墙规则,以便在工作虚拟机的这些端口上启用流量:

$ gcloud compute firewall-rules create \
  kubernetes-cluster-allow-external-ingress \
  --allow tcp:$HTTP_PORT,tcp:$HTTPS_PORT \
  --network kubernetes-cluster \
  --source-ranges 0.0.0.0/0

获取第一个工作线程的公共 IP 地址:

$ WORKER_IP=$(gcloud compute instances describe worker-0 \
  --zone $(gcloud config get-value compute/zone) \
  --format='get(networkInterfaces[0].accessConfigs[0].natIP)')

从您的本地计算机,您可以尝试连接到这些端口:

$ curl http://$WORKER_IP:$HTTP_PORT
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.7</center>
</body>
</html>
$ curl -k https://$WORKER_IP:$HTTPS_PORT
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.17.7</center>
</body>
</html>

如果您可以看到这些响应,说明入口控制器运行正常,您将被重定向到返回 404 错误的默认后端。

访问应用

现在,让我们创建一个简单的应用(Apache 服务器)并通过入口资源公开它:

$ kubectl create deployment webapp --image=httpd
deployment.apps/webapp created
$ kubectl expose deployment webapp --port 80
service/webapp exposed
$ kubectl apply -f - <<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: webapp-ingress
spec:
  backend:
    serviceName: webapp
    servicePort: 80
EOF

使用这种入口配置,所有对入口控制器的请求都将被路由到webapp服务:

$ curl http://$WORKER_IP:$HTTP_PORT/
<html><body><h1>It works!</h1></body></html>

现在,让我们通过部署第二个 web 应用,在同一个入口上复用几个应用,这次是kennship/http-echo映像:

$ kubectl create deployment echo --image=kennship/http-echo
deployment.apps/echo created
$ kubectl expose deployment echo --port 3000
service/echo exposed
$ kubectl delete ingress webapp-ingress
ingress.extensions "webapp-ingress" deleted
$ kubectl apply -f - <<EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: plex-ingress
spec:
  rules:
  - host: webapp.com
    http:
      paths:
      - path: /
        backend:
          serviceName: webapp
          servicePort: 80
  - host: echo.com
    http:
      paths:
      - path: /
        backend:
          serviceName: echo
          servicePort: 3000
EOF

我们现在可以通过主机名来访问不同的应用:

$ curl -H 'Host: echo.com' http://$WORKER_IP:$HTTP_PORT/
{"path":"/","headers":{"host":"echo.com","x-request-id":"3502371d3a479598bc393aa61a8\
b6896","x-real-ip":"10.240.0.20","x-forwarded-for":"10.240.0.20","x-forwarded-host":\
"echo.com","x-forwarded-port":"80","x-forwarded-proto":"http","x-scheme":"http","use\
r-agent":"curl/7.58.0","accept":"*/*"},"method":"GET","body":{},"fresh":false,"hostn\
ame":"echo.com","ip":"::ffff:10.244.1.49","ips":[],"protocol":"http","query":{},"sub\
domains":[],"xhr":false}

$ curl -H 'Host: webapp.com' http://$WORKER_IP:$HTTP_PORT/

<html><body><h1>It works!</h1></body></html>

HTTPS 和入口

如果您尝试在入口控制器的 HTTPS 端口(此处为 31296)上使用 HTTPS 协议进行连接,您会发现入口控制器使用的是假证书:

$ curl -k -v -H 'Host: webapp.com' https://$WORKER_IP:$HTTPS_PORT/
[...]
* Server certificate:
*  subject: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
*  start date: Jan 17 16:59:01 2020 GMT
*  expire date: Jan 16 16:59:01 2021 GMT
*  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
[...]

让我们使用我们自己的证书,在这个例子中是一个自动生成的证书,但是这个过程和一个签名的证书是一样的。首先,生成证书,然后创建包含证书的tls秘密:

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -out echo-ingress-tls.crt \
    -keyout echo-ingress-tls.key \
    -subj "/CN=echo.com/O=echo-ingress-tls"
[...]
$ kubectl create secret tls echo-ingress-tls \
    --key echo-ingress-tls.key \
    --cert echo-ingress-tls.crt
secret/echo-ingress-tls created

然后向Ingress资源添加一个部分:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: plex-ingress
spec:
  tls:
  - hosts:
    - echo.com
    secretName: echo-ingress-tls
  rules:
  - host: webapp.com
    http:
      paths:
      - path: /
        backend:
          serviceName: webapp
          servicePort: 80
  - host: echo.com
    http:
      paths:
      - path: /
        backend:
          serviceName: echo
          servicePort: 3000

通过这些更改,echo.com请求现在将使用这个新证书:

$ curl -k -v --resolve echo.com:$HTTPS_PORT:$WORKER_IP https://echo.com:$HTTPS_PORT/
[...]
* Server certificate:
*  subject: CN=echo.com; O=echo-ingress-tls
*  start date: Jan 17 18:10:33 2020 GMT
*  expire date: Jan 16 18:10:33 2021 GMT
*  issuer: CN=echo.com; O=echo-ingress-tls
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
[...]

webapp.com还是会用默认的:

$ curl -k -v --resolve webapp.com:$HTTPS_PORT:$WORKER_IP https://webapp.com:$HTTPS_PORT
[...]
* Server certificate:
*  subject: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
*  start date: Jan 17 16:59:01 2020 GMT
*  expire date: Jan 16 16:59:01 2021 GMT
*  issuer: O=Acme Co; CN=Kubernetes Ingress Controller Fake Certificate
[...]

Footnotes 1

https://kubernetes.github.io/ingress-nginx/deploy/

 

十一、安全

Kubernetes 是一个安全的系统:你首先需要被认证,作为一个普通用户或服务帐户;然后,授权系统会验证您是否有权执行所请求的操作。

此外,可以通过定义安全上下文来限制主机系统上的容器的权限,并通过定义网络策略来限制网络中的容器的权限。

证明

Kubernetes 定义了两种用户:普通用户服务账户

普通用户认证

普通用户不受 Kubernetes API 的管理。您必须有一个管理用户及其凭据的外部系统。普通用户的身份验证可以通过不同的方法来处理:

  • 客户证书

  • HTTP 基本身份验证

  • 不记名令牌

  • 认证代理

客户端证书身份验证

当使用kubeadm安装集群时,API 服务器配置有选项

--client-ca-file=/etc/kubernetes/pki/ca.crt

此选项是群集启用客户端证书身份验证所必需的。ca.crt包含认证机构。

对于新用户,用户的第一步是创建证书签名请求(CSR):

# Create a private key
$ openssl genrsa -out user.key 4096
[...]

创建csr.cnf配置文件以生成 CSR:

[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]
CN = user
O = company

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth

创建企业社会责任:

$ openssl req -config ./csr.cnf -new -key user.key -nodes -out user.csr

其次,集群管理员必须使用 Kubernetes API 提供的CertificateSigningRequest资源签署 CSR:

# Write user CSR as base64 data
$ export CSR=$(cat user.csr | base64 | tr -d '\n')

# Create a template for the CertificateSigningRequest
$ cat > user-csr.yaml <<EOF
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: user-csr
spec:
  groups:
  - system:authenticated
  request: ${CSR}
  usages:
  - digital signature
  - key encipherment
  - server auth
  - client auth
EOF

# Insert CSR data into the template and apply it
$ cat user-csr.yaml | envsubst | kubectl apply -f -
certificatesigningrequest.certificates.k8s.io/user-csr created

# Verify CSR resource is created
$ kubectl get certificatesigningrequests.certificates.k8s.io user-csr
NAME       AGE   REQUESTOR          CONDITION
user-csr   52s   kubernetes-admin   Pending

# Approve the certificate
$ kubectl certificate approve user-csr
certificatesigningrequest.certificates.k8s.io/user-csr approved

# Verify CSR is approved ans issued
$ kubectl get certificatesigningrequests.certificates.k8s.io user-csr
NAME       AGE     REQUESTOR          CONDITION
user-csr   2m17s   kubernetes-admin   Approved,Issued

# Extract the issued certificate
$ kubectl get csr user-csr -o jsonpath='{.status.certificate}'
  | base64 --decode > user.crt

第三,管理员必须为用户创建一个 kubeconfig file

kubeconfig 文件有三个部分:服务器信息、用户信息和上下文信息。

命令kubectl config set-cluster用于写入服务器信息。

  • 注意以下命令中出现的标志--kubeconfig=userconfig。当标志被设置时,kubectl工具将在这个文件上工作,而不是在默认的文件∽/.kube/config上工作:
$ CLUSTER_ENDPOINT=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.server}')
$ CLUSTER_CA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
$ echo -n $CLUSTER_CA | base64 -d > cluster.crt
$ CLUSTER_NAME=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].name}')
$ kubectl --kubeconfig=userconfig config set-cluster $CLUSTER_NAME \
   --server=$CLUSTER_ENDPOINT \
   --certificate-authority=cluster.crt --embed-certs
Cluster "cka" set.

命令kubectl config set-credentials用于写入用户信息:

$ USER_NAME=user
$ kubectl --kubeconfig=userconfig config set-credentials $USER_NAME \
   --client-certificate=user.crt --embed-certs
User "user" set.

命令kubectl config set-context用于写入上下文信息,kubectl config use-context用于选择当前上下文:

$ kubectl --kubeconfig=userconfig config set-context default \
   --cluster=$CLUSTER_NAME \
   --user=$USER_NAME \
   --namespace=default
Context "default" created.
$ kubectl --kubeconfig=userconfig config use-context default
Switched to context "default".

管理员可以使用以下命令授权用户读取有关节点的信息(在“授权”一节中有更多相关信息):

$ kubectl create clusterrole nodes-read \
   --verb=get,list,watch --resource=nodes
clusterrole.rbac.authorization.k8s.io/nodes-read  created
$ kubectl create clusterrolebinding user-nodes-read \
   --clusterrole=nodes-read --user=user
clusterrolebinding.rbac.authorization.k8s.io/user-nodes-read created

管理员现在可以将这个userconfig文件发送给用户。

用户需要使用kubectl config set-credentials命令在文件中添加关于他们私钥的信息:

$ USER_NAME=user
$ kubectl --kubeconfig=userconfig config set-credentials $USER_NAME  \
   --client-key=user.key --embed-certs

作为user,让我们尝试用这个userconfig文件列出节点和 pod:

$ kubectl --kubeconfig=userconfig get nodes
NAME         STATUS   ROLES    AGE   VERSION
controller   Ready    master   8h    v1.18.6
worker-0     Ready    <none>   8h    v1.18.6
worker-1     Ready    <none>   8h    v1.18.6

$ kubectl --kubeconfig=userconfig get pods
Error from server (Forbidden): pods is forbidden: User "user" cannot list resource "pods" in API group "" in the
namespace "default"

用户可以看到预期的节点,并且希望没有获得 pod 的权利,因为只有显式的给定访问才被授权。

HTTP 基本身份验证

为了使 Kubernetes API 服务器支持 HTTP 基本身份验证,您必须指定以下选项:

--basic-auth-file=somefile

让我们用以下内容创建/etc/kubernetes/pki/basic-auth文件:

$ echo mypassword,pmartin,pmartin | \
   sudo tee /etc/kubernetes/pki/basic-auth
$ sudo chmod 600 /etc/kubernetes/pki/basic-auth

并将该选项添加到/etc/kubernetes/manifests/kube-apiserver.yaml文件中:

[...]
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=10.240.0.10
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --basic-auth-file=/etc/kubernetes/pki/basic-auth
[...]

通过查看AGE信息,验证 API 服务器是否重新启动以使更改生效:

$ kubectl get pods -n kube-system kube-apiserver-controller
NAME                        READY   STATUS   RESTARTS   AGE
kube-apiserver-controller   1/1     Running  3          15s

现在用户pmartin已经在 API 服务器中注册了,让我们为这个用户创建一个 kubeconfig 文件。

kubeconfig 文件有三个部分:服务器信息、用户信息和上下文信息。

命令kubectl config set-cluster用于写入服务器信息。

  • 注意以下命令中出现的标志--kubeconfig=userconfig。当标志被设置时,kubectl工具将在这个文件上工作,而不是在默认的文件∽/.kube/config上工作:
$ CLUSTER_ENDPOINT=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.server}')
$ CLUSTER_CA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
$ echo -n $CLUSTER_CA | base64 -d > cluster.crt
$ CLUSTER_NAME=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].name}')
$ kubectl --kubeconfig=userconfig config set-cluster $CLUSTER_NAME \
   --server=$CLUSTER_ENDPOINT \
   --certificate-authority=cluster.crt --embed-certs
Cluster "cka" set.

命令kubectl config set-credentials用于写入用户信息:

$ USER_NAME=pmartin
$ kubectl --kubeconfig=userconfig config set-credentials $USER_NAME \
   --username=pmartin --password=mypassword
User "pmartin" set.

命令kubectl config set-context用于写入上下文信息,kubectl config use-context用于选择当前上下文:

$ kubectl --kubeconfig=userconfig config set-context default \
   --cluster=$CLUSTER_NAME \
   --user=$USER_NAME \
   --namespace=default
Context "default" created.
$ kubectl --kubeconfig=userconfig config use-context default
Switched to context "default".

管理员可以使用以下命令授权用户读取有关节点的信息(在“授权”一节中有更多相关信息):

$ kubectl create clusterrole nodes-read \
   --verb=get,list,watch --resource=nodes
clusterrole.rbac.authorization.k8s.io/nodes-read created
$ kubectl create clusterrolebinding pmartin-nodes-read \
   --clusterrole=nodes-read --user=pmartin
clusterrolebinding.rbac.authorization.k8s.io/pmartin-nodes-read created

管理员现在可以将这个userconfig文件发送给用户。

作为pmartin,让我们尝试用这个userconfig文件列出节点和 pod:

$ kubectl --kubeconfig=userconfig get nodes
NAME         STATUS   ROLES    AGE   VERSION
controller   Ready    master   8h    v1.18.6
worker-0     Ready    <none>   8h    v1.18.6
worker-1     Ready    <none>   8h    v1.18.6

$ kubectl --kubeconfig=userconfig get pods
Error from server (Forbidden): pods is forbidden: User "pmartin" cannot list resource "pods" in API group "" in the namespace "default"

用户可以看到预期的节点,并且希望没有获得 pod 的权利,因为只有显式的给定访问才被授权。

不记名令牌认证

为了使 Kubernetes API 服务器支持不记名令牌认证,您必须指定以下选项:

--token-auth-file=somefile

让我们用以下内容创建/etc/kubernetes/pki/tokens文件:

$ echo 22C1192A24CE822DDB2CB578BBBD8,foobar,foobar | \
   sudo tee /etc/kubernetes/pki/tokens
$ sudo chmod 600 /etc/kubernetes/pki/tokens

并将该选项添加到/etc/kubernetes/manifests/kube-apiserver.yaml文件中:

[...]
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=10.240.0.10
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --token-auth-file=/etc/kubernetes/pki/tokens
[...]

通过查看AGE信息,验证 API 服务器是否重新启动以使更改生效:

$ kubectl get pods -n kube-system kube-apiserver-controller
NAME                        READY   STATUS   RESTARTS   AGE
kube-apiserver-controller   1/1     Running  3          15s

现在用户foobar已经在 API 服务器中注册了,让我们为这个用户创建一个 kubeconfig 文件。

kubeconfig 文件有三个部分:服务器信息、用户信息和上下文信息。

命令kubectl config set-cluster用于写入服务器信息。

  • 注意以下命令中出现的标志--kubeconfig=userconfig。当标志被设置时,kubectl工具将在这个文件上工作,而不是在默认的文件∽/.kube/config上工作:
$ CLUSTER_ENDPOINT=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.server}')
$ CLUSTER_CA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
$ echo -n $CLUSTER_CA | base64 -d > cluster.crt
$ CLUSTER_NAME=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].name}')
$ kubectl --kubeconfig=userconfig config set-cluster $CLUSTER_NAME \
   --server=$CLUSTER_ENDPOINT \
   --certificate-authority=cluster.crt --embed-certs
Cluster "cka" set.

命令kubectl config set-credentials用于写入用户信息:

$ USER_NAME=foobar
$ kubectl --kubeconfig=userconfig config set-credentials $USER_NAME \
   --token=22C1192A24CE822DDB2CB578BBBD8
User "foobar" set.

命令kubectl config set-context用于写入上下文信息,kubectl config use-context用于选择当前上下文:

$ kubectl --kubeconfig=userconfig config set-context default \
   --cluster=$CLUSTER_NAME \
   --user=$USER_NAME \
   --namespace=default
Context "default" created.
$ kubectl --kubeconfig=userconfig config use-context default
Switched to context "default".

管理员可以使用以下命令授权用户读取有关节点的信息(在“授权”一节中有更多相关信息):

$ kubectl create clusterrole nodes-read \
   --verb=get,list,watch --resource=nodes
clusterrole.rbac.authorization.k8s.io/nodes-read created
$ kubectl create clusterrolebinding foobar-nodes-read \
   --clusterrole=nodes-read --user=foobar
clusterrolebinding.rbac.authorization.k8s.io/foobar-nodes-read created

管理员现在可以将这个userconfig文件发送给用户。

作为foobar,让我们尝试用这个userconfig文件列出节点和 pod:

$ kubectl --kubeconfig=userconfig get nodes
NAME         STATUS   ROLES    AGE   VERSION
controller   Ready    master   8h    v1.18.6
worker-0     Ready    <none>   8h    v1.18.6
worker-1     Ready    <none>   8h    v1.18.6

$ kubectl --kubeconfig=userconfig get pods
Error from server (Forbidden): pods is forbidden: User "foobar" cannot list resource\
"pods" in API group "" in the namespace "default"

用户可以看到预期的节点,并且希望没有获得 pod 的权利,因为只有显式的给定访问才被授权。

服务帐户身份验证

与普通用户不同,服务帐户由 Kubernetes API 管理。身份验证由 JSON Web 令牌(jwt)处理。

当在一个名称空间中创建一个ServiceAccount时,令牌控制器在同一个名称空间中创建一个Secret,其名称以服务帐户名为前缀,并用 API 服务器的公共 CA、一个签名令牌和当前名称空间的名称填充它。

当一个名称空间被创建时,服务帐户控制器在这个名称空间中创建一个default服务帐户。这种创建又导致了相关联的Secret的创建。

Pod 规范的serviceAccountName字段指示哪个服务帐户被附加到 Pod。默认情况下,如果不指定任何服务帐户名,其值为default。您可以将 Pod 规范的automountServiceAccountToken字段设置为false,以指示不应该使用任何服务帐户。

与 Pod 的服务帐户相关联的Secret被自动装载到 Pod 文件系统中一个众所周知的目录中。Pod 中的 Kubernetes 客户机知道这个路径,并使用这些凭证连接到 API 服务器。

例如,让我们创建一个服务帐户,并将其用于包含kubectl命令的容器,以测试来自容器内部的访问:

# Create a service account for a kubectl pod
$ kubectl create serviceaccount kubectl
serviceaccount/kubectl created

# Get the name of the associated secret
$ SECRET_NAME=$(kubectl get sa kubectl -o jsonpath='{.secrets[0].name}')

# Show the secret contents
$ kubectl get secret $SECRET_NAME -o yaml
[...]

# Create the nodes-read cluster role
$ kubectl create clusterrole nodes-read \
   --verb=get,list,watch --resource=nodes
clusterrole.rbac.authorization.k8s.io/nodes-read created

# Bind the nodes-read role to the service account kubectl in default namespace
$ kubectl create clusterrolebinding default-kubectl-nodes-read
   --clusterrole=nodes-read --serviceaccount=default:kubectl
clusterrolebinding.rbac.authorization.k8s.io/default-kubectl-nodes-read created

# Execute kubectl container with kubectl service account
$ kubectl run kubectl \
   --image=bitnami/kubectl:latest \
   --serviceaccount=kubectl \
   --command sh -- -c "sleep $((10**10))"
pod/kubectl created

# Connect into the kubectl container
$ kubectl exec -it kubectl bash

# Get nodes
$ kubectl get nodes
NAME         STATUS   ROLES   AGE   VERSION
controller   Ready    master  10h   v1.18.6
worker-0     Ready    <none>  10h   v1.18.6
worker-1     Ready    <none>  10h   v1.18.6

# Try to get pods
$ kubectl get pods
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:kubectl" cannot list resource "pods"
in API group

"" in the namespace "default"

# Show the mounted files from service account secret
$ ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt namespace token

群集外的服务帐户

请注意,与服务帐户相关联的令牌也可以从群集外部使用。例如,您可以创建一个包含这个令牌的 kubeconfig 文件,并在您的开发机器上使用它:

$ CLUSTER_ENDPOINT=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.server}')
$ CLUSTER_CA=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
$ echo -n $CLUSTER_CA | base64 -d > cluster.crt
$ CLUSTER_NAME=$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].name}')
$ kubectl --kubeconfig=saconfig config set-cluster $CLUSTER_NAME \
   --server=$CLUSTER_ENDPOINT \
   --certificate-authority=cluster.crt --embed-certs
Cluster "cka" set.

$ USER_NAME=kubectl

$ SECRET_NAME=$(kubectl get sa kubectl -o jsonpath='{.secrets[0].name}')
$ TOKEN=$(kubectl get secrets $SECRET_NAME -o jsonpath='{.data.token}' | base64 -d)
$ kubectl --kubeconfig=saconfig config set-credentials $USER_NAME \
   --token=$TOKEN
User "kubectl" set.

$ kubectl --kubeconfig=saconfig config set-context default \
   --cluster=$CLUSTER_NAME \
   --user=$USER_NAME \
   --namespace=default
Context "default" created.
$ kubectl --kubeconfig=saconfig config use-context default
Switched to context "default".

# List the nodes
$ kubectl --kubeconfig=saconfig get nodes
NAME         STATUS   ROLES   AGE   VERSION
controller   Ready    master  10h   v1.18.6
worker-0     Ready    <none>  10h   v1.18.6
worker-1     Ready    <none>  10h   v1.18.6

# Try to list the pods
$ kubectl --kubeconfig=saconfig get pods
Error from server (Forbidden): pods is forbidden

: User "system:serviceaccount:default:kubectl" cannot list resource
"pods" in API group "" in the namespace "default"

批准

Kubernetes API 是一个 REST API。在请求级别评估对操作的授权。用户必须被授权访问该请求的所有部分,而不是该请求的所有授权策略。准入控制器可用于微调授权给用户的请求部分。

授权由模块管理,可以同时安装几个模块。每个模块都按顺序检查,第一次允许或拒绝请求会停止授权过程。如果没有模块对该请求有意见,则该请求被拒绝。

以下模块可用:

  • ABAC:基于属性的访问控制

  • RBAC:基于角色的访问控制

  • web book:http 回调模式

  • 节点:kubelets 的专用模块

  • AlwaysDeny:出于测试目的,阻止所有请求

  • AlwaysAllow:完全禁用授权

要选择要使用的模块,您必须在apiserver服务的--authorization-mode标志中指定它们的名称。对于安装了 kubeadm 的集群,标志的默认值是--authorization-mode=Node,RBAC

API 服务器请求的剖析

资源请求

资源请求用于对 Kubernetes 资源进行操作。

资源请求的端点具有以下形式

  • /api/v1/...获取核心组的资源

  • 其他 API 的资源

命名空间资源的端点形式如下

  • /api/v1/namespaces/<namespace>/<resource>

  • /apis/<group>/<version>/namespaces/<namespace>/<resource>

n 个非命名空间资源的端点的形式如下

  • /api/v1/<resource>

  • /apis/<group>/<version>/<resource>

对于资源请求,使用了一个 API 请求动词。常见的动词有

  • create创建新的资源对象

  • update用新的资源对象替换资源对象

  • patch更改资源对象的特定字段

  • get检索资源对象

  • list检索一个名称空间内或跨所有名称空间的所有资源对象

  • watch流式处理对象上的事件(创建、更新、删除)

  • delete删除资源对象

  • deletecollection删除一个名称空间内的所有资源对象

您可以使用命令kubectl api-resources -o wide获得所有资源的列表,包括它们的 API 组和它们支持的动词,以及它们是否有命名空间。

非资源请求

非资源请求是所有其他请求,用于访问有关集群的信息。

对于非资源请求,使用一个 HTTP 请求动词,对应于小写的 HTTP 方法,如getpostputdelete

请求授权属性

授权需要考虑的属性有

  • 来自身份验证的属性:

    • user:认证用户

    • group:用户所属的组名列表

    • extra:认证层提供的键值对

  • 请求的属性:

    • 对于资源请求:

      • API 组(核心组为空)

      • 名称空间(仅用于命名空间资源请求)

      • 资源

      • 资源名称(对于getupdatepatchdelete动词是必需的)

      • 子资源名称(可选)

      • API 请求动词

    • 对于非资源请求:

      • 请求路径

      • HTTP 请求动词

RBAC 模式

RBAC 模式引入了两个概念:定义权限列表的角色,以及将角色绑定到一个用户或一组用户(普通用户、组或服务帐户)的角色绑定

角色和集群角色

定义了两个资源,这取决于角色是用Role在名称空间内定义的还是用ClusterRole在集群范围内定义的。

Role 和 ClusterRole 规范包含一个rules字段,一组包含这些子字段的PolicyRule结构:

  • verbs:允许的动词列表,或VerbAll(或 YAML 的*)绕过该字段的验证

  • apiGroups:允许的 API 组列表,或APIGroupAll(或 YAML 的*)绕过该字段的验证

  • resources:允许资源列表,或ResourceAll(或 YAML 的*)绕过该字段的验证

  • resourceNames:允许对象的列表,或空以绕过该字段的验证

  • nonResourceURLs:允许的非资源 URL 列表(仅适用于 ClusterRole),或者为空以绕过对此字段的验证

每个 PolicyRule 代表要授予该角色的一组权限。

对于角色允许的请求,请求的动词、API 组、资源、资源名称和非资源 URL 属性(如果适用)必须出现在角色的任何策略规则的相应字段中(当验证对该字段有效时)。

角色用于授予对定义角色的命名空间中的命名空间资源的访问权。

群集角色用于授予对的访问权限

  • 任何命名空间中的命名空间资源(如 pod)

  • 跨所有名称空间的命名空间资源(如 pod )(使用–all-namespaces 标志)

  • 没有命名空间的资源(如节点)

  • 非资源端点(如/ healthz)

RoleBinding 和 ClusterRoleBinding

RoleBindingClusterRoleBinding规范都包含一个roleRef字段(一个RoleRef结构)和一个subjects字段(一个Subject结构的数组)。

这些角色绑定将被引用的角色授予指定的主体。

roleRef字段引用

  • 对于 ClusterRoleBinding,为 ClusterRole

  • 对于 RoleBinding、ClusterRole 或同一命名空间中的角色

RoleRef结构由字段组成

  • apiGroup:必须是rbac.authorization.k8s.io

  • 种类:必须是RoleClusterRole

  • 名称:角色或集群角色的名称

Subject结构由字段组成

  • apiGroup:" "表示服务帐户,rbac.authorization.k8s.io表示UserGroup

  • 种类:必须是UserGroupServiceAccount

  • 名称:User/Group/ServiceAccount主题的名称

  • 名称空间:主题的名称空间,用于ServiceAccount

不同的可能绑定有

  • 引用一个Role的一个RoleBinding:在角色和角色绑定的命名空间中提供对命名空间资源的访问

  • 引用一个ClusterRole的一个RoleBinding:允许访问角色绑定的名称空间中的命名空间资源(用于在不同主题的不同名称空间中重用一个集群角色)

  • 引用一个ClusterRole的一个ClusterRoleBinding:允许访问所有命名空间中的命名空间资源和跨所有命名空间的资源、非命名空间资源和非资源端点

例子

以下是一些角色定义及其含义的示例。

role-read角色允许用户获取并列出default名称空间中的所有命名空间资源:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: role-read
  namespace: default
rules:
- apiGroups:
  - "*"
  resources:
  - "*"
  verbs:
  - "get"
  - "list"

---


apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: role-read
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-read
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user

您也可以在命令模式下创建这些资源:

$ kubectl create role role-read --verb=get,list --resource="*.*"
role.rbac.authorization.k8s.io/role-read created
$ kubectl create rolebinding role-read --role=role-read --user=user
rolebinding.rbac.authorization.k8s.io/role-read created

这些请求将被批准:

$ export KUBECONFIG=userconfig

$ kubectl get pods kubectl # get core/pods

$ kubectl get pods # list core/pods

$ kubectl get deployments # list extensions/deployments

这些请求将不会被授权:

$ export KUBECONFIG=userconfig

$ kubectl get namespaces # namespaces are cluster-scope

$ kubectl delete pods kubectl # delete not in verbs

$ kubectl get pods -n kube-system # not in default namespace

现在,您可以删除角色和角色绑定:

$ unset KUBECONFIG
$ kubectl delete rolebinding role-read
rolebinding.rbac.authorization.k8s.io "role-read" deleted
$ kubectl delete role role-read
role.rbac.authorization.k8s.io "role-read" deleted

role-create-pod角色允许用户在default名称空间中创建窗格:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: role-create-pod
rules:
- apiGroups:
  - ""
  resources:
  - "pods"
  verbs:
  - "create"

---


apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: role-create-pod
roleRef:
  apiGroup: rbac.authorization.k8s.io

  kind: Role
  name: role-create-pod
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user

您也可以在命令模式下创建这些资源:

$ kubectl create role role-create-pod --resource=pods --verb=create
role.rbac.authorization.k8s.io/role-create-pod created
$ kubectl create rolebinding role-create-pod --role=role-create-pod --user=user
rolebinding.rbac.authorization.k8s.io/role-create-pod created

该请求将被批准:

$ export KUBECONFIG=userconfig
$ kubectl run nginx --image=nginx # create core/pods

这些请求将不会被授权:

$ export KUBECONFIG=userconfig
$ kubectl get pods # list verb
$ kubectl get pods nginx # get verb
$ kubectl create deployment nginx --image=nginx # extensions/deployments
$ kubectl run nginx --image=nginx -n other # other namespace

现在,您可以删除角色和角色绑定:

$ unset KUBECONFIG
$ kubectl delete rolebinding role-create-pod
rolebinding.rbac.authorization.k8s.io "role-create-pod" deleted
$ kubectl delete role role-create-pod
role.rbac.authorization.k8s.io "role-create-pod" deleted

cluster-role-read角色允许用户获取并列出所有命名空间中的所有命名空间资源,以及所有非命名空间资源:

apiVersion: rbac.authorization.k8s.io/v1
kind:ClusterRole
metadata:
  name: cluster-role-read
rules:
- apiGroups:
  - "*"
  resources:
  - "*"
  verbs:
  - "get"
  - "list"

---


apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-role-read
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind:ClusterRole
  name: cluster-role-read
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: user

您也可以在命令模式下创建这些资源:

$ kubectl create clusterrole cluster-role-read --resource="*.*" --verb=get,list
clusterrole.rbac.authorization.k8s.io/cluster-role-read created
$ kubectl create clusterrolebinding cluster-role-read --clusterrole=cluster-role-rea\
d --user=user
clusterrolebinding.rbac.authorization.k8s.io/cluster-role-read created

这些请求将被批准:

$ export KUBECONFIG=userconfig

$ kubectl get pods kubectl # get core/pods

$ kubectl get pods # list core/pods

$ kubectl get pods -n kube-system # list core/pods in other namespace

$ kubectl get pods -A # list core/pods across all namespaces

$ kubectl get deployments # list extensions/deployments

$ kubectl get nodes # list (non-namespaced) nodes

这些请求将不会被授权:

$ export KUBECONFIG=userconfig

$ kubectl delete pods kubectl # delete not in verbs

现在,您可以删除角色和角色绑定:

$ unset KUBECONFIG
$ kubectl delete rolebinding cluster-role-read
rolebinding.rbac.authorization.k8s.io "cluster-role-read" deleted
$ kubectl delete role cluster-role-read
role.rbac.authorization.k8s.io "cluster-role-read" deleted

安全上下文

您可以在 Pod 和容器级别配置安全上下文。

在 Pod 级别

Pod 规范在其PodSecurityContext结构中定义了几个字段,可在securityContext字段中访问。

用户和组

默认情况下,Pod 容器内的进程以root权限运行。由于容器隔离,容器内的root权限受到限制。

但是在某些情况下,例如,当在容器中挂载外部文件系统时,您希望进程以特定的用户和组权限运行。

runAsNonRoot字段有助于确保 Pod 容器中的进程作为非根用户运行。如果在容器映像定义中没有定义用户,并且在这里或者在容器级别的SecurityContext中没有定义runAsUSer,kubelet 将拒绝启动 Pod。

通过runAsUserrunAsGroupsupplementalGroups字段,您可以将 Pod 容器的第一个流程影响到特定用户和特定组,并将这些流程添加到补充组中。

例如,当运行具有以下规格的 Pod 时:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: box
  name: box
spec:
  selector:
    matchLabels:
      app: box
  template:
    metadata:
      labels:
        app: box
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1001
        supplementalGroups:
        - 1002
        - 1003
      containers:
      - image: busybox
        name: box
        command:
        - sh
        - c
        - "touch  /tmp/ready  &&   sleep  $((10**10))"

您可以检查用户和组的进程和创建的文件:

$ ps -o pid,user,group,comm
PID   USER     GROUP    COMMAND
    1 1000     1001     sleep
    7 1000     1001     sh
   14 1000     1001     ps
$  ls -l /tmp/ready
-rw-r--r--  1 1000  1001  0 Jan 23 09:52 /tmp/ready
$ id
uid=1000 gid=1001 groups=1002,1003

SELinux 选项

seLinuxOptions将 SELinux 上下文应用于 Pod 的所有容器。

sysctls

如果您想从容器内部设置内核参数,您将得到以下错误:

$ sysctl -w kernel.shm_rmid_forced=1
sysctl: error setting key 'kernel.shm_rmid_forced': Read-only file system

您可以从 Pod 规格中传递这些值:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: box
  name: box
spec:
  selector:
    matchLabels:
      app: box
  template:
    metadata:
      labels:
        app: box
    spec:
      securityContext:
        sysctls:
        - name: kernel.shm_rmid_forced
          value: "1"
      containers:
      - image: busybox
        name: box

注意,默认情况下只允许更改安全系统。如果您想更改其他系统,您需要通过 kubelet 配置(使用kubelet --allowed-unsafe-sysctls 'comma-separated list of sysctls')来允许它们。

在容器层面

容器规范在其SecurityContext结构中定义了几个字段,可在securityContext字段中访问。

用户和组

在 Pod 规范中,您可以使用runAsNonRoot字段断言映像作为非根用户运行,并且您可以使用runAsGrouprunAsUser为容器的第一个进程指定特定的用户和组。

如果在 Pod 和容器级别都指定了,则使用容器规范中的信息。

SELinux 选项

将 SELinux 上下文应用于该容器。如果在 Pod 和容器级别都指定了,则使用容器规范中的信息。

能力

  • capabilities

    修改容器初始流程的初始功能

  • allowPrivilegeEscalation

    指示流程是否可以在运行时获得更多功能

其他人

  • privileged

    这相当于在主机中以 root 用户身份运行。除非你确切知道自己在做什么,否则不要这样做。

  • readOnlyRootFilesystem

    这些进程将无法在容器文件系统中更改或创建文件。例如,这可以防止攻击者在容器中安装新程序。

网络策略

默认情况下,集群单元之间的流量不受限制。您可以使用NetworkPolicy资源,通过声明网络策略来微调 pod 之间的流量授权。

NetworkPolicy资源的规范包含这些字段

  • podSelector:选择此策略适用的窗格。空值匹配命名空间中的所有窗格。

  • policyTypes:表示您是否要应用Ingress规则、Egress规则或两者都应用。

  • ingress:所选 pod 的允许进入规则。

  • egress:所选 pod 的允许出口规则。

首先,创建三个 Pod(一个 Apache 服务器、一个 nginx 服务器和一个 busybox Pod)和两个服务来公开这两个 web 服务器:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: box-deployment
spec:
  selector:
    matchLabels:
      app: box
  template:
    metadata:
      labels:
        app: box
    spec:
      containers:
      - name: box
        image: busybox
        command:
        - sh
        - c
        - "sleep $((10**10))"

---


apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd-deployment
spec:
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd

    spec:
      containers:
      - name: httpd
        image: httpd

---


apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx

---


apiVersion: v1
kind:  Service
metadata:
  name: httpd-service
spec:
  type: ClusterIP
  selector:
    app: httpd
  ports:
  - port: 80

---


apiVersion: v1
kind:  Service
metadata:
  name: nginx-service
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
  - port: 80

您可以看到,从box容器中,您可以访问apachenginx:

$ kubectl exec -it box-deployment-xxxxxxxxxx-yyyyy sh

# wget -q http://httpd-service -O -
[... apache response ...]

# wget -q http://nginx-service -O -
[... nginx response ...]

通过第一个NetworkPolicy,您可以禁止所有进入 pod 的流量:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: netpol
spec:
  podSelector: {}
  policyTypes:
  - Ingress

您可以看到您无法再从box吊舱连接到apachenginx吊舱:

$ kubectl exec -it box-deployment-xxxxxxxxxx-yyyyy sh

# wget -q http://httpd-service -O -
<no reply>

# wget -q http://nginx-service -O -
<no reply>

您现在可以允许流量从box箱流向nginx的端口 80:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: netpol2
spec:
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
    - Ingress
  ingress:
  - ports:
    - port: 80
    from:
    - podSelector:
        matchLabels:
          app: box

使用私有 Docker 注册表

到目前为止,您已经引用了公共映像来部署容器。但是,当您想要部署自己的应用时,您可能不想公开您的容器映像,而是将它们存储在私有注册表中。

在这种情况下,kubelet 将需要获得必要的凭证,以便能够下载存储在私有注册表中的映像。

使用 imagePullSecrets

当您无权访问节点或自动创建节点时,建议使用这种方式来访问注册表。

第一步是创建一个包含注册表凭证的Secret

如果您已经登录或者可以使用命令docker login从您的计算机登录到注册表,那么您应该有一个文件∽/.docker/config.json。然后,您可以使用命令从这个文件创建一个Secret

$ kubectl create secret generic regcred \
    --from-file=.dockerconfigjson=<path/to/.docker/config.json> \
    --type=kubernetes.io/dockerconfigjson

或者您可以从登录信息中创建一个Secret:

$ kubectl create secret docker-registry regcred \
   --docker-server=$DOCKER_REGISTRY_SERVER \
   --docker-username=$DOCKER_USER \
   --docker-password=$DOCKER_PASSWORD \
   --docker-email=$DOCKER_EMAIL

一旦在名称空间中创建了秘密,您就可以从带有imagePullSecrets字段的 Pod 规范中引用它(注意,如果您的 Pod 包含几个容器,那么您可以引用几个秘密,从不同的注册中心获取映像):

# box.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: box
  name: box
spec:
  selector:
    matchLabels:
      app: box
  template:
    metadata:
      labels:
        app: box
    spec:
      imagePullSecrets:
      - name: regcred
      containers:
      - image: yourlogin/test
        name: box

需要知道的一件重要的事情是,一旦映像被一个节点下载,在这个相同节点上执行的所有 Pods 将被允许使用这个映像,即使它们没有指定一个imagePullSecrets

要对其进行测试,首先在多工作集群上部署以下 Pod,并查看它部署在哪个节点上:

$ kubectl apply -f box.yaml
deployment.apps/box created
$ kubectl get pods -o wide
NAME                   READY   STATUS   [...]   NODE
box-865486655c-c76sj   1/1     Running  [...]   worker-0

在这种情况下,映像已经由worker-0下载。现在更新部署,删除imagePullSecrets并部署几个副本,这样它们也会被部署到其他节点上。还将imagePullPolicy设置为IfNotPresent,这样 kubelet 将使用已经存在的映像(如果可用的话):

$ kubectl delete -f box.yaml
deployment.apps "box" deleted
$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: box
  name: box
spec:
  replicas: 2
  selector:
    matchLabels:
      app: box
  template:
    metadata:
      labels:
        app: box
    spec:
      containers:
      - image: yourlogin/test
        name: box
        imagePullPolicy: IfNotPresent
EOF
deployment.apps/box created
$ kubectl get pods -o wide
NAME                   READY   STATUS         AGE   [...]   NODE
box-865486655c-n4pg6   0/1     ErrImagePull   5s    [...]   worker-1
box-865486655c-w55kp   1/1     Running        5s    [...]   worker-0

您可以看到worker-0中的 Pod 成功启动,但是另一个 worker 中的 Pod 未能获得映像。

在节点上预拉映像

您已经看到,只需要将一个映像拖到节点上,就可以用于窗格。所以让我们连接到worker-1节点,连接到 Docker 注册表,然后手动拉取映像:

$ gcloud compute ssh worker-1
Welcome to worker-1

$ sudo docker login
Username: yourlogin
Password:

$ docker pull yourlogin/test
docker.io/yourlogin/test:latest

如果你回到你的计算机,你可以看到worker-1中的 Pod 现在可以启动了:

$ kubectl get pods -o wide
NAME                   READY   STATUS   [...]   NODE
box-865486655c-2t6fw   1/1     Running  [...]   worker-1
box-865486655c-nnpr2   1/1     Running  [...]   worker-0

给库伯莱颁发证书

在上一步中,您登录了私有注册表来手动下载映像。在登录过程中,Docker 创建了一个∾/.docker/config.json来存储登录过程中使用的凭证。

您可以将该文件复制到 kubelet 识别的目录中,因此 kubelet 将尝试使用这些凭证下载映像:

$ gcloud compute ssh worker-1
Welcome to worker-1

$ cp $HOME/.docker/config.json /var/lib/kubelet/

$ sudo systemctl kubelet restart