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

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

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

协议:CC BY-NC-SA 4.0

一、使用 Kubernetes 创建集群

在本章中,您将在 Google Cloud 中的虚拟机上部署一个 Kubernetes 集群。

调配计算资源

您将安装一个控制面板集群。为此,您将需要一个用于控制器的虚拟机和几个(这里是两个)用于工作者的虚拟机。

集群中所有机器之间的完全网络连接是必要的。为此,您将创建一个虚拟私有云(VPC)来托管集群,并定义一个子网来获取主机地址。

从 Google Cloud 控制台,创建一个新项目my-project;,然后从本地终端登录并设置当前区域、区域和项目(您也可以从 Google Cloud Shell 工作并跳过登录步骤):

$ gcloud auth login
[...]
$ gcloud config set compute/region us-west1
Updated property [compute/region].
$ gcloud config set compute/zone us-west1-c
Updated property [compute/zone].
$ gcloud config set project my-project
Updated property [core/project].

创建专用虚拟私有云(VPC):

$ gcloud compute networks create kubernetes-cluster --subnet-mode custom
Created [https://www.googleapis.com/compute/v1/projects/my-project/global/networks/kubernetes-cluster].

kubernetes-cluster VPC 中创建子网:

$ gcloud compute networks subnets create kubernetes \
  --network kubernetes-cluster \
  --range 10.240.0.0/24
Created [https://www.googleapis.com/compute/v1/projects/my-project/regions/us-west1/subnetworks/kubernetes].

为内部通信创建防火墙规则:

$ gcloud compute firewall-rules create \
  kubernetes-cluster-allow-internal \
  --allow tcp,udp,icmp \
  --network kubernetes-cluster \
  --source-ranges 10.240.0.0/24,10.244.0.0/16
Created [https://www.googleapis.com/compute/v1/projects/my-project/global/firewalls/kubernetes-cluster-allow-internal].

为外部通信创建防火墙规则:

$ gcloud compute firewall-rules create \
  kubernetes-cluster-allow-external \
  --allow tcp:22,tcp:6443,icmp \
  --network kubernetes-cluster \
  --source-ranges 0.0.0.0/0
Created [https://www.googleapis.com/compute/v1/projects/my-project/global/firewalls/kubernetes-cluster-allow-external].

为控制器保留一个公共 IP 地址:

$ gcloud compute addresses create kubernetes-controller \
  --region $(gcloud config get-value compute/region)
Created [https://www.googleapis.com/compute/v1/projects/my-project/regions/us-west1/addresses/kubernetes-controller].
$ PUBLIC_IP=$(gcloud compute addresses describe kubernetes-controller \
    --region $(gcloud config get-value compute/region) \
    --format 'value(address)')

为控制器创建一个虚拟机:

$ gcloud compute instances create controller \
    --async \
    --boot-disk-size 200GB \
    --can-ip-forward \
    --image-family ubuntu-1804-lts \
    --image-project ubuntu-os-cloud \
    --machine-type n1-standard-1 \
    --private-network-ip 10.240.0.10 \
    --scopes compute-rw,storage-ro,service-management,service-control, logging-write, monitoring \
    --subnet kubernetes \
    --address $PUBLIC_IP
Instance creation in progress for [controller]: [...]

为员工创建虚拟机:

$ for i in 0 1; do \
  gcloud compute instances create worker-${i} \
    --async \
    --boot-disk-size 200GB \
    --can-ip-forward \
    --image-family ubuntu-1804-lts \
    --image-project ubuntu-os-cloud \
    --machine-type n1-standard-1 \
    --private-network-ip 10.240.0.2${i} \
    --scopes compute-rw,storage-ro,service-management,service-control,logging-write, monitoring \
    --subnet kubernetes; \
done
Instance creation in progress for [worker-0]: [...]

Instance creation in progress for [worker-1]: [...]

在主机上安装 Docker

对控制器和每个工人重复这些步骤。

连接到主机(这里是控制器):

$ gcloud compute ssh controller

安装 Docker 服务:

# Install packages to allow apt to use a repository over HTTPS
$ sudo apt-get update && sudo apt-get install -y \
  apt-transport-https ca-certificates curl software-properties-common

# Add Docker's official GPG key
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# Add Docker apt repository
$ sudo add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"

# List available versions of Docker
$ apt-cache madison docker-ce

# Install Docker CE, for example version 5:19.03.12~3-0
$ sudo apt-get update && sudo apt-get install -y \
  docker-ce=5:19.03.12~3-0~ubuntu-bionic \
  docker-ce-cli=5:19.03.12~3-0~ubuntu-bionic

$ sudo apt-mark hold containerd.io docker-ce docker-ce-cli

# Setup daemon
$ cat <<EOF | sudo tee /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF

$ sudo mkdir -p /etc/systemd/system/docker.service.d

# Restart docker
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
$ sudo systemctl enable docker

在主机上安装 kubeadm、kubelet 和 kubectl

对控制器和每个工人重复这些步骤。

连接到主机(这里是控制器):

$ gcloud compute ssh controller

安装kubelet , kubeadm ,kubectl:

# Add GPG key
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -

# Add Kubernetes apt repository
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF

# Get Kubernetes apt repository data
$ sudo apt-get update

# List available versions of kubeadm
$ apt-cache madison kubeadm

# Install selected version (here 1.18.6-00)
$ sudo apt-get install -y kubelet=1.18.6-00 kubeadm=1.18.6-00 kubectl=1.18.6-00
$ sudo apt-mark hold kubelet kubeadm kubectl

初始化控制面板节点

仅在控制器上运行这些步骤。

初始化集群(这需要几分钟时间):

$ gcloud config set compute/zone us-west1-c # or your selected zone
Updated property [compute/zone].
$ KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute instances describe controller \
  --zone $(gcloud config get-value compute/zone) \
  --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
$ sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --ignore-preflight-errors=NumCPU \
  --apiserver-cert-extra-sans=$KUBERNETES_PUBLIC_ADDRESS

初始化结束时,会有一条消息向您发出一条命令,让您将工作者加入集群(这条命令以kubeadm join开头)。请复制此命令以备后用。

将安装生成的 kubeconfig 文件保存在您的主目录中。它将授予您对集群的管理员权限:

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
$ kubectl get nodes
NAME         STATUS      ROLES    AGE     VERSION
Controller   NotReady    master   1m14s   v1.18.6

安装calico Pod 网络插件:

$ kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml

等到安装结束。所有的吊舱都应该有一个运行状态:

$ kubectl get pods -A

当所有的吊舱都运行时,主节点应该就绪:

$ kubectl get nodes
NAME         STATUS  ROLES    AGE     VERSION
Controller   Ready   master   2m23s   v1.18.6

加入工人队伍

对每个工人重复这些步骤。

在控制器上运行kubeadm init后,运行您保存的命令:

$ sudo kubeadm join 10.240.0.10:6443 --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash>

如果您没有保存命令,您必须获得令牌和散列。在控制器上,运行:

$ kubeadm token list
TOKEN                     TTL   EXPIRES
abcdef.ghijklmnopqrstuv   23h   2020-01-19T08:25:27Z

令牌在 24 小时后过期。如果您的过期,您可以创建一个新的:

$ kubeadm token create
bcdefg.hijklmnopqrstuvw

要获得hash值,您可以在控制器上运行以下命令:

$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
   openssl rsa -pubin -outform der 2>/dev/null | \
   openssl dgst -sha256 -hex | sed 's/^.* //'
8cb2de97839780a412b93877f8507ad6c94f73add17d5d7058e91741c9d5ec78

二、控制面板组件

Kubernetes 控制面板由以下部分组成

  • API 服务器kube-apiserver,Kubernetes 控制面板的前端

  • 键值存储etcd,所有集群数据的后备存储

  • 调度器kube-scheduler,它为新的 pod 选择运行的节点

  • 控制器管理器kube-controller-manager,它嵌入了所有控制器,包括节点控制器、复制控制器、端点控制器以及服务帐户和令牌控制器。

在每个节点上,运行的组件有

  • kubelet,确保受节点影响的 pod 正常运行

  • kube-proxy,维护节点上的网络规则,以满足Service需求

探索控制面板服务

kubelet服务作为 Unix 服务运行,它的状态和日志可以通过使用传统的systemd命令行工具来访问:

$ systemctl status kubelet
[...]
$ journalctl -u kubelet
[...]

其他服务运行在 Kubernetes 集群中,并且在kube-system名称空间中可见。您可以使用kubectl describe命令获得状态,使用kubectl logs命令获得日志:

$ kubectl get pods -n kube-system
etcd-controller
kube-apiserver-controller
kube-controller-manager-controller
kube-proxy-123456
kube-proxy-789012
kube-proxy-abcdef
kube-scheduler-controller

$ kubectl describe pods -n kube-system etcd-controller
[...]
$ kubectl logs -n kube-system etcd-controller
[...]

你可能想知道是什么魔力使 Kubernetes 控制飞机在 Kubernetes 本身中运行。这要归功于静态 Pod 特性,使用它可以直接给kubelet服务提供 Pod 定义。您可以在控制器的以下目录中找到 pod 的清单:

$ gcloud compute ssh controller
Welcome to controller
$ ls /etc/kubernetes/manifests/
etcd.yaml
kube-apiserver.yaml
kube-controller-manager.yaml
kube-scheduler.yaml

三、访问集群

在前面的章节中,您已经在 Kubernetes 主机上安装了kubectl,并从这些主机上使用它。使用kubectl命令的通常方式是将其安装在您的开发机器上。

在本章中,你将看到如何在你的机器上安装kubectl以及如何配置它来访问你在第一章中安装的集群。

在您的开发机器上安装 kubectl

根据您在开发计算机上运行的操作系统,请遵循以下指令之一:

Linux 操作系统

$ curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.6/bin/linux/amd64/kubectl
$ chmod +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl
# Test it is working correctly
$ kubectl version --client --short
Client Version: v1.18.6

苹果

$ curl -LO https://storage.googleapis.com/kubernetes

-release/release/v1.18.6/bin/darwin/amd64/kubectl
$ chmod +x ./kubectl
$ sudo mv ./kubectl /usr/local/bin/kubectl
# Test it is working correctly
$ kubectl version --client --short
Client Version: v1.18.6

Windows 操作系统

$ curl -LO https://storage.googleapis.com/kubernetes

-release/release/v1.18.6/bin/windows/amd64/kubectl.exe
# Move the binary into your PATH,
$ kubectl version --client --short
Client Version: v1.18.6

从开发机器访问集群

获取新集群的 kubeconfig 文件:

$ gcloud compute scp controller:~/.kube/config kubeconfig

在文件中更新 IP 地址以访问群集:

$ KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute instances describe controller \
  --zone $(gcloud config get-value compute/zone) \
  --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
$ sed -i "s/10.240.0.10/$KUBERNETES_PUBLIC_ADDRESS/" kubeconfig

如果您还没有 kubeconfig 文件,您可以将其复制到$HOME/.kube/config:

$ mv -i kubeconfig $HOME/.kube/config

如果您已经有一个 kubeconfig 文件,您可以将这个新文件与现有文件合并。首先,检查kubeconfig文件:

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: <...>
    server: https://<ip>:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
    - name: kubernetes-admin
      user:
        client-certificate-data: <...>
        client-key-data: <...>

您可以看到这个文件定义了一个名为kubernetes的集群、一个名为kubernetes-admin的用户和一个上下文kubernetes-admin@kubernetes

这些名称非常通用(如果您用 kubeadm 创建了几个集群,所有的 kubeconfig 文件都会用相同的名称定义这些元素)。我们将首先用更具体的内容替换它们:

$ sed -i 's/kubernetes/cka/' kubeconfig

然后,我们可以将这个文件与现有的$HOME/.kube/config文件合并:

$ KUBECONFIG=$HOME/.kube/config:kubeconfig \
  kubectl config view --merge --flatten > config \
  && mv config $HOME/.kube/config

最后,您可以将当前上下文切换到cka-admin@kubernetes:

$ kubectl config use-context cka-admin@kubernetes
Switched to context "cka-admin@kubernetes".

四、Kubernetes 资源

Kubernetes 以声明的方式工作:您在 Kubernetes API 的帮助下创建资源,这些对象存储在 etcd 存储中,控制器工作以确保您在这些对象中声明的内容正确地部署在您的基础设施中。

大部分资源由三部分组成:元数据、规范状态

规范是您提供给集群的规范。这是控制器将检查以知道该做什么的内容。

状态代表基础设施中资源的当前状态,由控制器观察。这是您将检查的内容,以了解资源在基础结构中是如何部署的。

元数据包含其他信息,比如资源的名称、它所属的名称空间、标签、注释等等。

命名空间

一些资源(称为命名空间资源)属于一个namespace。名称空间是一个逻辑分隔,资源的名称在名称空间中必须是唯一的。

RBAC 授权使用名称空间来基于资源的名称空间定义授权。

您可以使用以下命令创建新的名称空间:

$ kubectl create namespace my-ns
namespace/my-ns created

然后,在运行kubectl命令时,可以用标志--namespace指定命令操作的名称空间,或者用--all-namespaces(简称-A)指定命令在所有名称空间中操作。

您也可以指定想要使用的默认名称空间,使用:

$ kubectl config set-context \
   --current --namespace=my-ns
Context "minikube" modified.

标签和选择器

任何数量的键值对(命名标签)都可以附加到任何资源上。这些标签主要由 Kubernetes 的组件和工具使用,通过属性(由标签具体化)而不是名称来选择资源:

  • 许多kubectl命令接受一个--selector(简称-l)标志,该标志允许通过标签选择资源:

  • 将流量路由到 pod 的服务使用标签选择器选择 pod:

$ kubectl get pods -l app=nginx -A

  • 负责维持给定数量的 Pod 的部署使用标签选择器来查找它所负责的 Pod(参见第五章“Pod 控制器”一节):
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    # distribute traffic to all pods with this label
    app: nginx
  ports:
  - port: 80

  • 您可以使用标签选择器选择要在其上部署 Pod 的节点的属性(参见第九章“使用标签选择器在特定节点上调度 Pod”一节)。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    # the pods managed by the Deployment
    # are the ones with this label
    matchLabels:
      app: nginx
  template:
    metadata:
      # pods created by the Deployment
      # will have this label
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx

释文

注释是附加到资源上的元数据,通常用于工具和 Kubernetes 扩展,但是还没有集成到 spec 部分中。您可以通过强制方式向资源添加注释:

$ kubectl annotate deployments.apps nginx \
   mytool/mem-size=1G
deployment.apps/nginx annotated

或者以声明的方式:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    mytool/mem-size: 1G
  name: nginx
[...]

五、工作负载

POD 是库伯内特集群架构的杰作。

Kubernetes 的基本目标是帮助您管理您的容器。Pod 是 Kubernetes 集群中可部署的最小部分,包含一个或多个容器。

kubectl命令行,您可以像运行以下命令一样简单地运行包含容器的 Pod:

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

通过将--dry-run=client -o yaml添加到命令中,您可以看到创建相同 Pod 所需编写的 YAML 模板:

$ kubectl run nginx --image=nginx --dry-run=client -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}

或者,您可以通过仅保留必填字段来大大简化模板:

-- simple.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx

现在,您可以使用以下模板启动 Pod:

$ kubectl apply -f simple.yaml
pod/nginx created

如果你不太挑剔的话,创建的 Pod 已经准备好了。否则,Pod 会提供一个很长的字段列表,使其更适合生产。这些都是字段。

在 specs 的下面

以下是 Pod 规格字段的分类:

  • 容器字段将更精确地定义和参数化 Pod 的每个容器,无论它是普通容器(containers)还是初始化容器(initContainers)。imagePullSecrets字段将帮助从私人注册处下载容器映像。

  • 字段(volumes)将定义容器能够挂载和共享的卷列表。

  • 调度字段将帮助您定义部署 Pod 的最合适的节点,方法是通过标签选择节点(nodeSelector)、直接指定节点名(nodeName)、使用affinitytolerations、选择特定的调度程序(schedulerName)以及要求特定的运行时类(runtimeClassName)。它们还将被用于确定一个 Pod 相对于其他 Pod 的优先级(priorityClassNamepriority)。

  • 生命周期字段将帮助定义一个 Pod 是否应该在终止(restartPolicy)后重启,并微调终止 Pod 的容器中运行的进程被终止(terminationGracePeriodSeconds)或正在运行的 Pod 如果尚未终止将停止(activeDeadlineSeconds)的时间段。它们还有助于定义 Pod 的就绪状态(readinessGates)。

  • 主机名和名称解析字段将帮助定义 Pod 的主机名(hostname和部分 FQDN ( subdomain),在容器(hostAliases)的 /etc/hosts 文件中添加主机,微调容器(dnsConfig)的 /etc/resolv.conf 文件,并定义 DNS 配置的策略(dnsPolicy)。

  • 主机名称空间字段将帮助指示 Pod 是否必须为网络(hostNetwork)、PID(hostPID)和 IPC ( hostIPC)使用主机名称空间,以及容器是否将共享相同的(非主机)进程名称空间(shareProcessNamespace)。

  • 服务帐户字段将有助于通过影响特定服务帐户(serviceAccountName)或通过automountServiceAccountToken禁用默认服务帐户的自动装载来赋予 Pod 特定权限。

  • 安全上下文字段(securityContext)有助于在 Pod 级别定义各种安全属性和通用容器设置。

容器规格

Pod 定义的一个重要部分是它将包含的容器的定义。

我们可以将容器字段分成两部分。第一部分包含与容器运行时相关的字段(映像、入口点、端口、环境变量和卷);第二部分包含将由 Kubernetes 系统处理的字段。

与容器运行时相关的字段如下:

  • 映像字段定义了容器的映像(image)和拉映像的策略(imagePullPolicy)。

  • 入口点字段定义了入口点的命令(command)和参数(args)及其工作目录(workingDir)。

  • 端口字段(ports)定义了从容器中暴露的端口列表。

  • 环境变量字段帮助定义将直接(env)或通过引用配置图或秘密值(envFrom)在容器中导出的环境变量。

  • 字段定义要装入容器的卷,无论它们是文件系统卷(volumeMounts)还是原始块卷(volumeDevices)。

与 Kubernetes 相关的字段如下:

  • 资源字段(resources)帮助定义容器的资源需求和限制。

  • 生命周期字段帮助定义生命周期事件(lifecycle)的处理程序,参数化终止消息(terminationMessagePathterminationMessagePolicy),并定义探测器来检查容器的活性(livenessProbe和就绪(readinessProbe)。

  • 安全上下文字段帮助在容器级别定义各种安全属性和通用容器设置。

  • 调试字段是非常专业的字段,主要用于调试目的(stdinstdinOncetty)。

Pod 控制器

POD,虽然是库伯内特建筑的杰作,但很少单独使用。您通常会使用一个控制器来运行带有一些特定策略的 Pod。

不同的控制器处理盒如下:

  • ReplicaSet:确保指定数量的 Pod 副本在任何给定时间运行。

  • Deployment:启用 pod 和副本集的声明性更新。

  • 管理 pod 和副本集的更新,照顾有状态的资源。

  • DaemonSet:确保所有或部分节点运行一个 Pod 的副本。

  • 启动 pod 并确保它们完成。

  • CronJob:根据时间表创建作业。

在 Kubernetes 中,所有控制器都遵循协调循环的原则:控制器持续观察一些感兴趣的对象,以便能够检测集群的实际状态(运行到集群中的对象)是否满足控制器负责的不同对象的规格,并相应地调整集群。

让我们仔细看看复制集和部署控制器是如何工作的。

复制集控制器

副本集的字段如下:

  • replicas表示您想要多少个所选 POD 的复制品。

  • selector定义您想要 ReplicaSet 控制器管理的 Pods。

  • template是当控制器检测到副本数量不足时,用于创建新 pod 的模板。

  • minReadySeconds表示控制器在 Pod 启动后等待的秒数,不认为 Pod 准备就绪。

复制集控制器持续地观察带有用selector指定的标签的容器。在任何给定的时间,如果带有这些标签的实际运行吊舱的数量

  • 大于请求的replicas,一些 pod 将被终止以满足副本的数量。请注意,终止的 pod 不一定是由 ReplicaSet 控制器创建的 pod。

  • 低于请求的replicas,将使用指定的 Pod template创建新的 Pod,以满足副本的数量。注意,为了避免 ReplicaSet 控制器在循环中创建 Pod,指定的template必须创建一个可由指定的selector选择的 Pod(这就是为什么您必须在selector.matchLabelstemplate.metadata.labels字段中设置相同的标签)。

注意到

  • 副本集的selector字段是不可变的。

  • 更改副本集的template不会立即生效。它将影响在这个变化之后将被创造的荚。

  • 更改replicas字段将立即触发 pod 的创建或终止。

部署控制器

部署的字段如下:

  • replicas表示请求的副本数量。

  • selector定义您希望部署控制器管理的单元。

  • template是 pod 所需的模板。

  • minReadySeconds表示控制器在 Pod 启动后等待的秒数,不认为 Pod 准备就绪。

  • strategy是在改变先前和当前激活的ReplicaSetsreplicas时应用的策略。

  • revisionHistoryLimit是为将来使用而保留的ReplicaSets的编号。

  • paused表示部署是否处于活动状态。

  • progressDeadlineSeconds

部署控制器永久地观察带有请求selector的复制集。其中包括

  • 如果具有所请求的template的副本集存在,控制器将确保该副本集的副本数量等于所请求的replicas(通过使用所请求的strategy),并且minReadySeconds等于所请求的数量。

  • 如果不存在具有所请求的template的副本集,控制器将用所请求的replicasselectortemplateminReadySeconds创建一个新的副本集。

  • 对于具有不匹配template的复制集,控制器将确保replicas的数量被设置为零(通过使用请求的strategy)。

注意到

  • 部署的selector字段是不可变的。

  • 更改部署的template字段将立即

    • 如果不存在具有所请求的selectortemplate的副本集,则触发新副本集的创建

    • 或者用所请求的replicas更新与所请求的selectortemplate相匹配的现有数据库(使用strategy

    • 或者将其他复制集的replicas的数量设置为零

  • 更改部署的replicasminReadySeconds字段将会立即更新相应副本集(具有所请求的template的副本集)的相应值。

使用这种方法,部署控制器管理一系列的副本集,每个副本集对应 Pod 模板的一个修订版。活动副本集是副本数量为正数的副本集,其他修订版的副本数量设置为零。

这样,通过将 Pod 模板从一个版本切换到另一个版本,您可以从一个版本切换到另一个版本(例如,用于回滚)。

更新和回滚

让我们首先在Deployment的帮助下部署 nginx 服务器的映像:

$ kubectl create deployment nginx --image=nginx:1.10
deployment.apps/nginx created

命令kubectl rollout提供了几个子命令来处理部署。

子命令status给出了部署的状态:

$ kubectl rollout status deployment nginx
deployment "nginx" successfully rolled out

history子命令为我们提供了部署的修订历史。这是部署的第一个版本:

$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>

我们现在将更新nginx的映像以使用1.11版本。一种方法是使用kubectl set image命令:

$ kubectl set image deployment nginx nginx=nginx:1.11
deployment.extensions/nginx image updated

通过history子命令,我们可以看到部署处于其第二个版本:

$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

默认情况下,change-cause为空。它可以包含用于制作卷展栏的命令,或者通过使用--record标志

$ kubectl set image deployment nginx nginx=nginx:1.12 --record
deployment.apps/nginx image updated
$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         kubectl set image deployment nginx nginx=nginx:1.12 --record=true

或者通过设置卷展栏后的kubernetes.io/change-cause注释:

$ kubectl set image deployment nginx nginx=nginx:1.13
deployment.apps/nginx image updated
$ kubectl annotate deployment nginx \
  kubernetes.io/change-cause="update to revision 1.13" \
  --record=false --overwrite=true
$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         kubectl set image deployment nginx nginx=nginx:1.12 --record=true
4         update to revision 1.13

还可以编辑部署的规范:

$ kubectl edit deployment nginx

在您喜欢的编辑器打开后,您可以,例如,添加一个环境变量FOO=bar到容器的规范中:

[...]
    spec:
      containers:
      - image: nginx:1.13
        env:
      - name: FOO
        value: bar
[...]

保存模板并退出编辑器后,新的修订版将被部署。让我们验证新 Pod 是否包含此环境变量:

$ kubectl describe pod -l app=nginx
[...]
    Environment:
      FOO:  bar
[...]

让我们为该版本设置一个变更原因,并查看历史记录:

$ kubectl annotate deployment nginx \
  kubernetes.io/change-cause="add FOO environment variable" \
  --record=false --overwrite=true
$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         kubectl set image deployment nginx nginx=nginx:1.12 --record=true
4         update to revision 1.13
5         add FOO environment variable

现在让我们用undo子命令回滚上一次的卷展栏:

$ kubectl rollout undo deployment nginx
deployment.apps/nginx rolled back
$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         kubectl set image deployment nginx nginx=nginx:1.12 --record=true
5         add FOO envvar
6         update to revision 1.13

我们看到我们切换回了第四个版本(它在列表中消失了,并被重命名为第六个修订版)。

也可以回滚到特定的版本,例如,再次使用nginx:1.12映像:

$ kubectl rollout undo deployment nginx --to-revision=3
deployment.apps/nginx rolled back
$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
5         add FOO envvar
6         update to revision 1.13
7         kubectl set image deployment nginx nginx=nginx:1.12 --record=true

最后,您可以验证每个修订都有一个ReplicaSet:

$ kubectl get replicaset
NAME               DESIRED CURRENT READY AGE
nginx-65c8969d67   0       0       0     58m
nginx-68b47b4d58   0       0       0     62m
nginx-7856959c59   1       1       1     62m
nginx-84c7fd7848   0       0       0     62m
nginx-857df58577   0       0       0     62m

部署策略

您已经在“部署控制器”一节中看到,当改变旧的和新的副本集的副本数量时,部署控制器提供了不同的策略

再造战略

最简单的策略是 Recreate 策略:在这种情况下,旧的副本集将被缩小到零,当该副本集的所有单元停止时,新的副本集将被扩大到所请求的副本数。

一些后果如下:

  • 将有一个小的停机时间,旧的吊舱停止,新的吊舱开始。

  • 并行运行以前的和新的 pod 不需要额外的资源。

  • 新旧版本不会同时运行。

滚动更新策略

RollingUpdate 策略是一种更高级的策略,也是创建部署时的默认策略。

该策略的目标是在不停机的情况下从以前的版本更新到新版本。

该策略将缩小和扩大复制集的可能性与通过服务公开 pod 的可能性结合起来。

你将在第十章中看到传统上通过服务访问 pod。服务资源声明了一个端点列表,这些端点是通过该服务公开的 pod 列表。当服务的端点没有准备好服务请求时,pod 被从服务的端点移除,当服务的端点准备好服务请求时,pod 被添加。

Pod 的就绪状态由为其容器声明的就绪探测器的状态决定。如果您没有为您的容器声明就绪探测器,那么风险在于,在它们真正就绪之前,会检测到这些容器就绪,并且当它们仍处于启动阶段时,会向它们发送流量。

在滚动更新期间,部署控制器一方面将

  • 增加新版本副本的数量

    当副本准备好时,它将被端点控制器添加到服务端点。

另一方面

  • 将旧版本的副本标记为未就绪,因此它们将由端点控制器从服务端点中移除

  • 阻止这些复制品

根据流量和可用资源,您可能希望先增加新版本副本的数量,然后停止旧副本,或者相反,先停止旧副本,然后启动新版本副本。

为此,部署strategy字段的字段maxSurgemaxUnavailable分别指示除了和少于预期数量的副本之外可以存在多少副本。根据这些值,部署控制器要么首先启动新版本,要么首先停止旧版本。

运行作业

作业和 CronJob 控制器帮助您运行 pod,直到它们按照基于时间的计划完成。

作业控制器

作业控制器并行运行一个或多个 pod,并等待特定数量的 pod 成功终止。

运行一个 Pod 直到完成

最简单的情况是运行一个 Pod 直到它完成。在这种情况下,您必须用spec.template声明要运行的 Pod 的模板,作业控制器将从该模板启动一个 Pod。

如果过了一段时间,Pod 成功终止(这意味着所有容器都以零状态退出),则该作业被标记为已完成。

但是如果 Pod 错误退出,新的 Pod 将重新启动,直到 Pod 成功或出现给定数量的错误。重试次数由spec.backoffLimit的值决定。

下面是一个将运行至完成的作业示例:

apiVersion: batch/v1
kind: Job
metadata:
  name: a-job
spec:
  template:
    spec:
      containers:
      - name: a-job
        image: bash
        command: ["bash", "-c", "sleep 1; exit 0" ]
      restartPolicy: Never

几秒钟后,您会看到 Pod 状态为Completed,作业状态为succeeded:

$ kubectl get pods
NAME          READY   STATUS     RESTARTS   AGE
a-job-sgd8r   0/1     Completed  0          8s

$ kubectl get jobs
NAME    COMPLETIONS   DURATION   AGE
a-job   1/1           4s         60s

$ kubectl get jobs a-job -o yaml
[...]
status:
  completionTime: "2020-08-15T09:19:07Z"
  conditions:
  - lastProbeTime: "2020-08-15T09:19:07Z"
    lastTransitionTime: "2020-08-15T09:19:07Z"
    status: "True"
    type: Complete
  startTime: "2020-08-15T09:19:03Z"
  succeeded: 1

现在,这是一个经常失败的工作的例子:

apiVersion: batch/v1
kind: Job
metadata:
  name: a-job-failing
spec:
  template:
    spec:
      containers:
      - name: a-job-failing
        image: bash
        command: ["bash", "-c", "sleep 1; exit 1" ]
      restartPolicy: Never
  backoffLimit: 3

过了一会儿,您可以看到启动了四个单元(一次加三次重试),所有这些单元都失败了,作业被标记为失败:

$ kubectl get pods
NAME                  READY   STATUS   RESTARTS   AGE
a-job-failing-92qsj   0/1     Error    0          2m19s
a-job-failing-c5w9x   0/1     Error    0          2m5s
a-job-failing-g4bp9   0/1     Error    0          105s
a-job-failing-nwm4q   0/1     Error    0          2m15s

$ kubectl get jobs a-job-failing -o yaml
[...]
status:
  conditions:
  - lastProbeTime: "2020-08-15T09:26:14Z"
    lastTransitionTime: "2020-08-15T09:26:14Z"
    message: Job has reached the specified backoff limit

    reason: BackoffLimitExceeded
    status: "True"
    type: Failed
  failed: 4
  startTime: "2020-08-15T09:25:00Z"

运行几个 pod,直到一个完成

如果您指定了与spec.parallelism并行运行的 pod 的数量,并且没有在spec.completions中定义完成的数量,作业控制器将并行运行这个数量的 pod,直到一个成功。

在第一个 Pod 成功之前的时间内,失败的 Pod 将由其他 Pod 替换。一旦一个 Pod 成功,控制器将等待其他 Pod 终止(错误或成功),并将作业标记为成功。

在此示例中,并行运行四个 pod,随机退出错误或成功,您可以检查控制器如何操作:

apiVersion: batch/v1
kind: Job
metadata:
  name: a-job
spec:
  template:
    spec:
      containers:
      - name: a-job
        image: bash
        command: ["bash", "-c", "sleep $((RANDOM/1024+1)); exit $((RANDOM/16384))" ]
  restartPolicy: Never

parallelism: 4

$ kubectl apply -f job-parallel.yaml && kubectl get pods -w
job.batch/a-job created
NAME         READY  STATUS             RESTARTS AGE
a-job-aaaaa  0/1    ContainerCreating  0        2s
a-job-bbbbb  0/1    ContainerCreating  0        2s
a-job-ccccc  0/1    ContainerCreating  0        2s
a-job-ddddd  0/1    ContainerCreating  0        3s
a-job-aaaaa  1/1    Running            0        5s
a-job-bbbbb  1/1    Running            0        6s
a-job-ccccc  1/1    Running            0        7s
a-job-ddddd  1/1    Running            0        8s
a-job-bbbbb  0/1    Error              0        12s b fails
a-job-BBBBB  0/1    Pending            0        0s B replaces b
a-job-BBBBB  0/1    ContainerCreating  0        0s
a-job-BBBBB  1/1    Running            0        3s
a-job-aaaaa  0/1    Error              0        19s a fails
a-job-AAAAA  0/1    Pending            0        0s A replaces a
a-job-AAAAA  0/1    ContainerCreating  0        0s
a-job-ddddd  0/1    Completed          0        22s d succeeds
a-job-AAAAA  1/1    Running            0        3s
a-job-AAAAA  0/1    Error              0        6s A fails, not replaced
a-job-ccccc  0/1    Error              0        29s c fails, not replaced

a-job-BBBBB  0/1    Completed          0        36s B succeeds

运行几个单元,直到几个单元完成

您可以使用spec.completions指定您希望成功的 pod 数量,使用spec.parallelism指定并行运行的 pod 的最大数量。

在所有情况下,并行运行的 pod 数量永远不会高于仍待完成的完成数量。例如,如果您指定一个4completions和一个parallelism为 6,控制器将首先启动四个吊舱(因为还有四个完成任务要完成)。当第一个 Pod 成功时,还剩下三个完成,如果已经有三个 Pod 在运行,控制器将不会启动新的 Pod。

在这个例子中,让我们检查一下控制器对于 4 的parallelism和 4 的completions是如何工作的:

apiVersion: batch/v1
kind: Job
metadata:
  name: a-job
spec:
  template: spec:
    containers:
    - name: a-job
      image: bash
      command: ["bash", "-c", "sleep $((RANDOM/1024+1)); exit $((RANDOM/16384))" ]
    restartPolicy: Never
  parallelism: 4
  completions: 4

$ kubectl apply -f job-parallel.yaml && kubectl get pods -w
job.batch/a-job created

NAME         READY  STATUS              RESTARTS  AGE
a-job-aaa01  0/1    ContainerCreating   0         1s
a-job-bbb01  0/1    ContainerCreating   0         1s
a-job-ccc01  0/1    ContainerCreating   0         1s
a-job-ddd01  0/1    ContainerCreating   0         1s
a-job-ccc01  1/1    Running             0         5s
a-job-bbb01  1/1    Running             0         5s
a-job-aaa01  1/1    Running             0         7s
a-job-ddd01  1/1    Running             0         9s
a-job-ccc01  0/1    Completed           0         11s c1 succeeds
a-job-ddd01  0/1    Completed           0         13s d1 succeeds
a-job-aaa01  0/1    Completed           0         17s a1 succeeds
a-job-bbb01  0/1    Error               0         28s b1 fails, replaced by b2
a-job-bbb02  0/1     Pending            0         0s
a-job-bbb02  0/1     ContainerCreating  0         0s
a-job-bbb02  1/1     Running            0         4s
a-job-bbb02  0/1     Error              0         30s b2 fails, replaced by b3

a-job-bbb03  0/1     Pending            0         0s
a-job-bbb03  0/1     ContainerCreating  0         0s
a-job-bbb03  1/1     Running            0         3s
a-job-bbb03   0/1     Completed         0         12s b3 succeeds

CronJob 控制器

CronJob 控制器允许您按照基于时间的计划运行作业。

创建CronJob模板时,两个必需的规范字段如下:

  • jobTemplate是您想要按基于时间的计划运行的作业的模板。

  • schedule是运行作业的时间规范,采用 Cron 格式。

concurrencyPolicy表示当前一个任务仍在运行时如何处理新任务。可能的值是Allow允许几个并发作业,Forbid如果前一个作业仍在运行则跳过新作业,Replace在运行新作业之前首先取消前一个作业。

如果您想暂时挂起一个特定的 CronJob,而不删除它,那么suspend布尔值非常有用。

计划格式

计划信息由五部分组成,代表执行作业的时间:

  • 分钟(0–59)

  • 小时(0–23)

  • 一个月中的第几天(1–31)

  • 月份(1–12)

  • 一周中的某一天(0–周日至 6–周六)

如果您不想限制在某个特定字段,可以使用星号*

符号*/n可用于每 n 个时间间隔运行一次作业,其中n是一个数字。

您可以用逗号分隔值来指定几个时间间隔。

示例:

  • 10 2 * * *将在每天凌晨 2:10 运行作业。

  • 将在每周日凌晨 3:30 运行该作业。

  • */15 7,8,9 * * *将在上午 7 点到 10 点(不包括)之间每 15 分钟运行一次作业。

六、配置应用

可以用不同的方式配置应用:

  • 通过向命令传递参数

  • 通过定义环境变量

  • 使用配置文件

命令的参数

我们在第五章“容器规范”一节中已经看到,我们可以用容器规范的Args字段来定义命令的参数。

不能通过使用kubectl命令runcreate来强制定义命令的参数。

根据映像的定义,您可能还必须指定Command值(尤其是当 Dockerfile 没有定义ENTRYPOINT而只定义了一个CMD)。

您必须在定义容器的模板中指定参数:

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

        name: nginx
        command: ["nginx"]
        args: ["-g", "daemon off; worker_priority 10;"]

环境变量

可以为容器定义环境变量,方法是直接声明它们的值,或者从ConfigMapsSecrets或所创建对象的字段(部署等)中引用它们的值。

直接声明值

以声明的形式

以声明方式,您可以将环境变量添加到容器的定义中:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        env:
        - name: VAR1
          value: "value1"
        - name: VAR2
          value: "value2"

以命令的形式

强制性地,使用kubectl run命令,您可以从命令行定义环境变量:

$ kubectl run nginx --image=nginx \
  --env VAR1=value1 \
  --env VAR2=value2
pod/nginx created

请注意,不推荐使用用于创建部署的kubectl run命令的变体,而推荐使用kubectl create deployment命令。不幸的是,这个命令不接受--env旗。您可以在创建部署后使用kubectl set env命令添加环境变量:

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl set env deployment nginx \
  --env VAR1=value1 \
  --env VAR2=value2
deployment.apps/nginx env updated

从配置映射和机密中引用特定值

以声明的形式

以声明方式,当声明环境变量时,您可以指示应该从 ConfigMap 或 Secret 中逐个提取值:

apiVersion: v1
kind: ConfigMap
metadata:
  name: vars
data:
  var1: value1
  var2: value2
---
apiVersion: v1
kind: Secret
metadata:
  name: passwords
stringData:
  pass1: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        env:
        - name: VAR1
          valueFrom:
            configMapKeyRef:
                key: var1
                name: vars
        - name: VAR2
          valueFrom:
            configMapKeyRef:
                key: var2
                name: vars
        - name: PASS1
          valueFrom:
            secretKeyRef:
                key: pass1
                name: passwords

请注意,如果在引用的配置映射或机密中找不到引用的密钥,部署的创建将会失败。如果您想要创建部署,即使值不存在(在这种情况下,相应的环境变量将不会被定义),您可以使用optional字段:

- name: PASS2
  valueFrom:
    secretKeyRef:
        key: pass2
        name: passwords
        optional: true

以命令的形式

强制性地,您也可以使用带有--fromkeys标志的kubectl set env命令。在本例中,您只引用了 ConfigMap 和 Secret 中定义的一些密钥:

$ kubectl create configmap vars \
  --from-literal=var1=value1 \
  --from-literal=var2=value2 \
  --from-literal=var3=value3
configmap/vars created
$ kubectl create secret generic passwords \
  --from-literal=pass1=foo \
  --from-literal=pass2=bar
secret/passwords created
$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl set env deployment nginx \
  --from=configmap/vars \
  --keys="var1,var2"
deployment.apps/nginx env updated
$ kubectl set env deployment nginx \
  --from=secret/passwords \
  --keys="pass1"
deployment.apps/nginx env updated

引用配置映射和机密中的所有值

以声明的形式

您还可以将配置映射或机密的所有条目作为环境变量注入(在这种情况下,您还可以使用optional字段来指示操作应该成功,即使所引用的配置映射或机密不存在):

apiVersion: v1
kind: ConfigMap
metadata:
  name: vars
data:
  var1: value1
  var2: value2
---
apiVersion: v1
kind: Secret
metadata:
  name: passwords
stringData:
  pass1: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx

  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app:  nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        envFrom:
        - configMapRef:
            name: vars
        - secretRef:
            name: passwords

        - secretRef:
            name: notfound
            optional: true

以命令的形式

强制性地,您也可以使用带有--from标志的kubectl set env命令:

$ kubectl create configmap vars \
  --from-literal=var1=value1 \
  --from-literal=var2=value2
configmap/vars created
$ kubectl create secret generic passwords \
  --from-literal=pass1=foo
secret/passwords created
$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl set env deployment nginx \
  --from=configmap/vars
deployment.apps/nginx env updated
$ kubectl set env deployment nginx \
  --from=secret/passwords
deployment.apps/nginx env updated

从 Pod 字段引用值

以声明方式,可以引用 Pod 的某些字段的值:

  • metadata.name

  • 元数据.命名空间

  • 元数据. uid

  • 规格节点名称

  • 服务帐户规格 Name

  • status.hostIP

  • status.podIP

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx

spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
                fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
                fieldPath: metadata.namespace
        - name: POD_UID
          valueFrom:
            fieldRef:
                fieldPath: metadata.uid
        - name:   POD_NODENAME
          valueFrom:
            fieldRef:
                fieldPath: spec.nodeName
        - name: POD_SERVICEACCOUNTNAME
          valueFrom:
            fieldRef:
                fieldPath: spec.serviceAccountName
        - name: POD_HOSTIP
          valueFrom:
            fieldRef:
                fieldPath: status.hostIP
        - name: POD_PODIP
          valueFrom:
            fieldRef:
                fieldPath: status.podIP

应用此模板后,您可以将环境变量的值检查到容器中:

$ kubectl exec -it nginx-xxxxxxxxxx-yyyyy bash -- -c "env | grep POD_"

从容器资源字段引用值

以声明方式,可以引用容器的资源请求和限制的值。您可以使用divisor字段将该值除以给定的除数:

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

        resources:
          requests:
            cpu: "500m"
            memory: "500Mi"
          limits:
            cpu: "1"
            memory: "1Gi"
        env:
        - name: M_CPU_REQUEST
          valueFrom:
            resourceFieldRef:
                resource: requests.cpu
                divisor: "0.001"
        - name: MEMORY_REQUEST
          valueFrom:
            resourceFieldRef:
                resource: requests.memory
        - name:  M_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
                resource: limits.cpu
                divisor: "0.001"
        - name: MEMORY_LIMIT
          valueFrom:
            resourceFieldRef:
                resource: limits.memory

变量将获得以下值:

M_CPU_REQUEST=500
MEMORY_REQUEST=524288000
M_CPU_LIMIT=1000
MEMORY_LIMIT=1073741824

来自配置映射的配置文件

可以在容器文件系统中挂载 ConfigMap 内容。挂载的配置映射的每个键/值将是一个文件名及其在挂载目录中的内容。

例如,您可以以声明形式创建此配置映射:

apiVersion: v1
kind: ConfigMap
metadata:
  name: config
data:
  nginx.conf: |
    server {
      location / {
        root /data/www;
      }

      locatioimg/ {
        root /data;
      }
    }

或以命令的形式:

$ cat > nginx.conf <<EOF
server {
    location / {
        root /data/www;
    }

    locatioimg/ {
        root /data;
    }
}
EOF
$ kubectl create configmap config --from-file=nginx.conf
configmap/config created

然后,您可以在容器中挂载配置映射:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas:  1
  selector:
    matchLabels:
      app: nginx
    template:
      metadata:
        labels:
          app: nginx
      spec:
        volumes:
        - name: config-volume
          configMap: config
        containers:
        - image: nginx
          name: nginx
          volumeMounts:
          - name: config-volume
            mountPath: /etc/nginx/conf.d/

最后,容器中的文件/etc/nginx/conf.d/nginx.conf将包含 ConfigMap 的nginx.conf键的值。

来自 Secret 的配置文件

同样,也可以挂载秘密的内容。首先,以声明的形式创建一个秘密

apiVersion: v1
kind: Secret
metadata:
  name: passwords
stringData:
  password: foobar

或祈使句:

$ kubectl create secret generic passwords \
  --from-literal=password=foobar
secret/passwords created

然后在容器中装入秘密:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx

  template:
    metadata:
      labels:
        app: nginx
    spec:
      volumes:
      - name: passwords-volume
        secret:
          secretName: passwords
          containers:
          - image: nginx
            name: nginx
            volumeMounts:
            - name: passwords-volume
              mountPath: /etc/passwords

最后,容器中的文件/etc/passwords/password将包含 foobar。

Pod 字段中的配置文件

以声明方式,可以装入包含 Pod 值的文件的卷:

  • metadata.name

  • 元数据.命名空间

  • 元数据. uid

  • 元数据.标签

  • 元数据.注释

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - name: pod-info
          mountPath: /pod
          readOnly: true
      volumes:
        - name: pod-info
          downwardAPI:
            items:
            - path: metadata/name
              fieldRef:
                fieldPath: metadata.name
            - path: metadata/namespace
              fieldRef:
                fieldPath: metadata.namespace
            - path: metadata/uid
              fieldRef:
                fieldPath: metadata.uid
            - path: metadata/labels
              fieldRef:
                fieldPath: metadata.labels
            - path: metadata/annotations
              fieldRef:
                fieldPath: metadata.annotations

因此,在容器中,您可以在/pod/metadata中找到文件:

$ kubectl exec nginx-xxxxxxxxxx-yyyyy bash -- -c \
  'for i in /pod/metadata/*; do echo $i; cat -n $i; echo ; done'
/pod/metadata/annotations
     1  kubernetes.io/config.seen="2020-01-11T17:21:40.497901295Z"
     2  kubernetes.io/config.source="api"
/pod/metadata/labels
     1  app="nginx"
     2  pod-template-hash="789ccf5b7b"
/pod/metadata/name
     1  nginx-xxxxxxxxxx-yyyyy
/pod/metadata/namespace
     1  default
/pod/metadata/uid
     1  631d01b2-eb1c-49dc-8c06-06d244f74ed4

容器资源字段中的配置文件

以声明的方式,可以用包含资源请求值和容器限制的文件来挂载卷。您可以使用divisor字段将该值除以给定的除数:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources:
          requests:
            cpu: "500m"
            memory: "500Mi"
          limits:
            cpu: "1"
            memory: "1Gi"
        volumeMounts:
        - name: resources-info
          mountPath: /resources
          readOnly: true
      volumes:
        - name: resources-info
          downwardAPI:
            items:
            - path: limits/cpu
              resourceFieldRef:
                resource: limits.cpu
                divisor: "0.001"
                containerName: nginx
            - path: limits/memory
              resourceFieldRef:
                resource: limits.memory
                containerName: nginx
            - path: requests/cpu
              resourceFieldRef:
                resource: requests.cpu
                divisor: "0.001"
                containerName: nginx
            - path: requests/memory
              resourceFieldRef:
                resource: requests.memory
                containerName: nginx

因此,在容器中,您可以在/resources中找到文件:

$ kubectl exec nginx-85d7c97f64-9knh9 bash -- -c \
  'for i in /resources/*/*; do echo $i; cat -n $i; echo ; done'
/resources/limits/cpu
     1  1000
/resources/limits/memory
     1  1073741824
/resources/requests/cpu

     1  500
/resources/requests/memory
     1  524288000

不同来源的配置文件

可以使用包含配置映射、机密、Pod 字段和容器资源字段混合信息的文件来装载卷。

与前面示例的主要区别在于,这里可以在同一目录中混合来自这些不同来源的值:

apiVersion: v1
kind: ConfigMap
metadata:
  name: values
data:
  cpu: "4000"
  memory: "17179869184"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx

        resources:
          requests:
            cpu: "500m"
            memory: "500Mi"
          limits:
            cpu: "1"
            memory: "1Gi"
        volumeMounts:
        - name: config
          mountPath: /config
          readOnly: true
      volumes:
        - name: config
          projected:
            sources:
            - configMap:
                name: values
                items:
                - key: cpu
                  path: cpu/value
                - key: memory
                  path: memory/value
            - downwardAPI:
                items:
                - path: cpu/limits
                  resourceFieldRef:
                    resource: limits.cpu
                    divisor: "0.001"
                    containerName: nginx
                - path: memory/limits
                  resourceFieldRef:
                    resource: limits.memory
                    containerName: nginx
                - path: cpu/requests
                  resourceFieldRef:
                    resource: requests.cpu
                    divisor: "0.001"
                    containerName: nginx
                - path: memory/requests
                  resourceFieldRef:
                    resource: requests.memory
                    containerName: nginx

因此,在容器中,您可以在/config中找到文件:

$ kubectl exec nginx-7d797b5788-xzw79 bash -- -c \
  'for i in /config/*/*; do echo $i; cat -n $i; echo ; done'
/config/cpu/limits
     1  1000
/config/cpu/requests
     1  500
/config/cpu/value
     1  4000
/config/memory/limits
     1  1073741824
/config/memory/requests
     1  524288000
/config/memory/value
     1  17179869184

七、扩展应用

我们已经在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

八、应用自修复

当您在群集上启动一个 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

十、发现和负载均衡

当您部署一个 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
[...]

十一、安全

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

十二、持久卷

持久卷( PV )是由集群管理员提供的存储资源。供应可以是手动或自动的。

PV 申报由两部分组成:

  • 其功能(访问模式、容量、存储类别、卷模式—文件系统或原始)

  • 其实现(本地、NFS、云存储资源等。)

这些存储资源旨在通过使用PersistentVolumeClaims供 Pod 使用:Pod 将声明一个具有特定功能的持久卷,Kubernetes 将尝试找到一个与这些功能匹配的持久卷(独立于实现细节)。

如果可用,持久卷将被安装到部署 Pod 的节点的文件系统中,并最终暴露给 Pod。实现部分向 kubelet 说明如何将存储挂载到文件系统中。

创建 NFS 持久性卷

例如,我们将手动创建一个由 NFS 卷实施的 PV。

首先,在谷歌云中创建一个 NFS 卷:

$ gcloud filestore instances create nfs-server \
   --project=$(gcloud config get-value project) \
   --zone=$(gcloud config get-value compute/zone) \
   --tier=STANDARD \
   --file-share=name="vol1",capacity=1TB \
   --network=name="kubernetes-cluster"
Waiting for [operation-...] to finish...done.
$ gcloud filestore instances describe nfs-server \
   --project=$(gcloud config get-value project) \
   --zone=$(gcloud config get-value compute/zone)
createTime: '2020-01-24T07:43:58.279881289Z'
fileShares:
- capacityGb: '1024'
  name: vol1
name: projects/yourproject/locations/us-west1-c/instances/nfs-server
networks:
- ipAddresses:
  - 172.25.52.106 # Note this IP address
  network: kubernetes-cluster
  reservedIpRange: 172.25.52.104/29
state: READY
tier: STANDARD

在 workers 上,安装 NFS 驱动程序,以便能够挂载 NFS 文件系统:

对每个工人重复这些步骤:

$ gcloud compute ssh worker-0
Welcome to worker-0
$ sudo apt-get -y update
$ sudo apt-get -y install nfs-common

您可以测试工作人员是否可以挂载文件系统:

$ gcloud compute ssh worker-0
Welcome to worker-0
$ sudo mkdir /mnt/nfs
$ sudo mount 172.25.52.106:/vol1 /mnt/nfs
$ ls /mnt/nfs
lost+found
$ sudo umount /mnt/nfs

现在,您可以定义 NFS 持久卷:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs
spec:
  # capabilities
  accessModes:
  - ReadWriteOnce
  - ReadOnlyMany
  - ReadWriteMany
  capacity:
    storage: 1Ti
  volumeMode: Filesystem
  # implementation
    nfs:
      path: /vol1
      server: 172.25.52.106

访问模式

一般来说,存储系统可以被访问以进行读写操作,并且可以或不可以被多个客户端同时访问以进行这些操作,具体取决于存储系统使用的技术。

PersistentVolume accessModes字段指示底层存储系统在只读或读/写模式下的同时访问能力。定义了三个值:

  • ReadWriteOnce (RWO)

  • 存储可由单个客户端进行读写操作。

  • readonlymny(rox)

  • 多个客户端可以对存储进行只读操作。

  • 读写多个 (RWX)

  • 多个客户端可以对存储进行读写操作。

如果 PV 具有能力,几个吊舱将能够同时使用这个 PV。请注意,对于要由几个 pod 使用的 PV,所有 pod 要求的访问模式必须相同;不可能一个 Pod 使用ReadOnlyMany模式,而另一个 Pod 在同一 PV 上使用ReadWriteMany

声明一个持久卷

当一个 Pod 需要一个持久卷时,它必须申请一个。它不要求特定的持久卷,而是要求一系列功能。永久卷控制器将根据匹配的功能影响最佳的永久卷。

使用了PersistentVolumeClaim资源,其规范结构定义了这些字段:

  • accessModes

  • 请求的访问模式(参见“访问模式”)。

  • selector

  • 基于标签匹配特定持久性卷的标签选择器。

  • resources

  • 永久卷必须至少提供resources.requests.storage,如果定义,最多提供resources.limits.storage

  • storageClassName

  • 存储提供商可以定义不同的存储类名;您可以指定永久卷应属于哪个类。

  • volumeMode

  • 存储方式:文件系统

继续前面的示例,您可以创建一个与调配的 NFS 持久性卷匹配的声明,确切地说,是一个可由多个客户端以读写模式访问的卷,具有至少 500 Gi 和最多 1.5 Ti 的存储:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pv-rwx-500g
spec:
  storageClassName: ""
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 500Gi
    limits:
      storage: 1500Gi

现在,您可以部署两个单元,使用相同的存储、一个数据库系统和一个机箱:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: pg
  name: pg
spec:
  serviceName: pg
  selector:
    matchLabels:
      app: pg
  template:
    metadata:
      labels:
        app: pg
    spec:
      containers:
      - image: postgres
        name: pg
        env:
        - name: PGDATA
          value: /var/lib/postgresql/data/db-files
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
      volumes:
      - name: data

        persistentVolumeClaim:
          claimName: pv-rwx-500g
          readOnly: false

---

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: box
  name: box
spec:
  serviceName: box
  selector:
    matchLabels:
      app: box
  template:
    metadata:
      labels:
        app: box
    spec:
      containers:
      - image: busybox
        name: box
        volumeMounts:
        - name: data
          mountPath: /mnt
        command:
          - sh
          - -c
          - "sleep $((10**10))"
        volumes:
        - name: data
          persistentVolumeClaim:
            claimName: pv-rwx-500g
            readOnly: false

如果您使用命令kubectl logs db-0检查数据库窗格的日志,您可以看到系统创建了一个新的数据库并将其文件存储在目录中:

/var/lib/postgresql/data/db-files

box窗格中,您可以访问相同的文件系统:

$ kubectl exec -it box-0 sh
# ls /mnt/db-files/
PG_VERSION            pg_multixact      pg_tblspc
base                  pg_notify         pg_twophase
global                pg_replslot       pg_wal
pg_commit_ts          pg_serial         pg_xact
pg_dynshmem           pg_snapshots      postgresql.auto.conf
pg_hba.conf           pg_stat           postgresql.conf
pg_ident.conf         pg_stat_tmp       postmaster.opts
pg_logical            pg_subtrans       postmaster.pid

清除

持久卷非常昂贵。当您不再需要 NFS 宗卷时,请记住将其删除:

$ gcloud filestore instances delete nfs-server \
   --project=$(gcloud config get-value project) \
   --zone=$(gcloud config get-value compute/zone)

使用自动配置的永久卷

通常,云中的 Kubernetes 引擎提供自动配置的持久卷。例如,在谷歌云 GKE 中,GCE 持久磁盘用于自动供应的持久卷。

首先,部署一个 Kubernetes 集群:

$ gcloud beta container clusters create "kluster" \
   --project $(gcloud config get-value project) \
   --zone $(gcloud config get-value compute/zone) \
   --cluster-version "1.15.12-gke.2" \
   --machine-type "g1-small" \
   --image-type "COS" \
   --disk-type "pd-standard" \
   --disk-size "30" \
   --num-nodes "1"
[...]
$ gcloud container clusters get-credentials kluster \
   --zone $(gcloud config get-value compute/zone) \
   --project $(gcloud config get-value project)

现在创建一个 PVC:

# auto-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: disk-rwo-10g
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

$ kubectl apply -f auto-pvc.yaml
persistentvolumeclaim/disk-rwo-10g created
$ kubectl get pvc
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
disk-rwo-10g   Bound   pvc-[...]   10Gi   RWO    standard    3s
$ kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM   \
   STORAGECLASS   REASON   AGE
pvc-[...]   10Gi    RWO    Delete   Bound    default/disk-rwo-10g\
   standard                3s

您可以看到,一旦创建了一个索赔,就创建了一个 PV 资源。

现在,您可以使用以下声明来部署 Pod:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: box
  name: box
spec:
  serviceName: box
  selector:
    matchLabels:
      app: box
  template:
    metadata:
      labels:
        app: box
    spec:
      containers:
      - image: busybox
        name: box
        volumeMounts:
        - name: data
          mountPath: /mnt
        command:
          - sh
          - -c
          - "sleep $((10**10))"
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: disk-rwo-10g
          readOnly: false

当您不再需要永久卷时,可以通过删除声明来释放它:

$ kubectl delete pvc disk-rwo-10g
persistentvolumeclaim "disk-rwo-10g" deleted
$ kubectl get pv
No resources found in default namespace.

清除

删除以前部署的 Kubernetes 集群:

$ gcloud beta container clusters delete "kluster" \
   --project $(gcloud config get-value project) \
   --zone $(gcloud config get-value compute/zone)

十三、多容器 Pod 设计模式

Pod 是 Kubernetes 集群中可部署的最小部分。一个 Pod 可以包含一个或多个容器。

当一个 Pod 包含几个容器时,这些容器共享网络和存储资源。

具有几个容器的单个容器必须小心使用。只有当容器紧密耦合,并且一个容器是主要容器,其他容器帮助第一个容器时,才应该使用这种模式。

Kubernetes 社区使用多容器 pod 描述了以下设计模式。

初始化容器

当您想要在主容器运行之前初始化一些资源或者等待一个特定的外部状态时,可以使用 Init 容器模式

Pod 资源的规范包含一个initContainers字段。该字段包含容器定义的数组。

通过该字段定义的 Init 容器在主容器之前按顺序运行。如果 init 容器失败,Pod 会立即失败。

初始化存储器

在第一个例子中,一个 Init 容器从 Google Cloud Bucket 加载文件,并将这些文件存储在一个 volatile 卷中。主容器 nginx 服务器挂载相同的易失性卷,并为这些文件提供服务:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: website
spec:
  selector:
    matchLabels:
      app: website
  template:
    metadata:
      labels:
        app: website
    spec:
      volumes:
      - name: static-files
        emptyDir:
          sizeLimit: 20Mi
      initContainers:
      - name: copy-static-files
        image: gcr.io/cloud-builders/gcloud
        command:
        - "bash"
        - "-c"
        - "gsutil cp -R $(SOURCE)/* /mnt/"
        env:
        - name: SOURCE
          value: gs://my-gcp-project/my-bucket
        volumeMounts:
        - mountPath: /mnt
          name: static-files
          readOnly: false
      containers:
      - name: website
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: static-files
          readOnly: true

等待其他服务的可用性

在第二个示例中,Init 容器测试后端服务是否可用,并在服务可用时成功终止,因此主容器可以认为后端在启动时正在运行:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      initContainers:
      - name: wait-backend
        image: busybox
        args:
        - sh

        - -c
        - "until wget http://backend/; do sleep 1; done"
      containers:
      - name: api
        image: api
        ports:
        - containerPort: 80

边车容器

边车容器是与主容器在同一个 Pod 中运行的附加容器,用于在主容器执行期间辅助主容器。

它的目的不是由设计模式定义的;例如,它可以在将入站流量发送到主容器之前拦截入站流量以对其进行调整,或者它可以拦截出站流量以对其进行调整以适应接收方,或者它可以聚合主容器创建的日志以公开它们,这只是举几个例子。

下面的两个设计模式,适配器大使,是 sidecar 容器模式的一个专门化。

适配器容器

适配器容器是一个边车容器,其目的是拦截到 Pod 的入站流量,并使其适应主容器所期望的协议。

使用适配器容器,您可以在不同的环境中重用主容器,而无需更改其接口。适配器容器将负责将调用者使用的协议转换为主容器使用的协议。

大使容器

大使容器是一个边车容器,其目的是代表主容器调用外部服务。

使用大使容器,您可以在不同的环境中重用主容器,而不需要让它知道与外部服务通信所需的所有协议,这些协议根据环境的不同可能具有不同的性质。

例如,考虑主容器需要从外部数据库访问数据。在开发环境中,您希望数据由内存中的数据库提供,类似于 SQLite。但是在生产环境中,数据必须由外部 PostgreSQL 数据库提供。由于大使容器模式,主容器将向其大使容器查询一些数据集,大使将根据环境进行专门化:部署在开发环境中的大使将从 SQLite 数据库查询数据,而部署在生产环境中的大使将从 PostgreSQL 数据库查询数据。

十四、可观察性

使用 Kubernetes 时,可观察性至关重要。Kubernetes 是由大量的移动部件组成的,你需要工具来了解这些不同部件之间发生了什么。

Kubernetes 级别的调试

首先,您必须注意到 Kubernetes 资源有两种类型:托管和非托管资源。托管资源是可识别的,因为它的定义包含一个spec部分和一个status部分。控制者负责管理这些资源;他们将阅读spec部分,将尽最大努力改变世界以反映这些规范,然后将在status部分报告状态。

其他资源,如ConfigMapSecretVolumeServiceAccountRoleRoleBinding等,都是非托管资源,它们的用途是包含系统其他元素使用的特定数据。

当您需要理解为什么您的应用不工作时,kubectl describe命令是首先使用的工具之一。使用这个命令,您可以观察所管理的 Kubernetes 资源的状态。

这个命令的输出通常从资源的元数据开始:名称、命名空间、标签和注释。元数据之后是规范和资源状态的混合,以人类可读的方式,通常以表格的形式。最后显示了附加到该资源的事件。

一个Event是 Kubernetes 资源,由控制器和其他 Kubernetes 元素用来记录信息。事件属于正常警告类型,指示事件发生的时间、采取的操作、采取操作的原因、哪个控制器采取了操作并发出了事件、事件涉及的资源以及事件的可读描述。

阅读这些事件,您通常会发现集群中某些问题的根本原因,包括以下内容:

  • Pod 不可调度,因为节点没有足够的可用资源。

  • 无法提取映像。

  • 卷、配置映射或密码不可用。

  • 容器的就绪或活动探测失败。

$ kubectl describe pods nginx-d9bc977d8-h66wf
Name:         nginx-d9bc977d8-h66wf
Namespace:    default
[...]
Events:
  Type    Reason      Age        From              Message
  ----    ------      ----       ----              -------
  Normal  Scheduled   <unknown>  default-scheduler Successfully assigned default/ngi\
nx-d9bc977d8-h66wf to worker-0
  Normal  Pulling     10s        kubelet, worker-0 Pulling image "nginx"
  Normal  Pulled      9s         kubelet, worker-0 Successfully pulled image "nginx"
  Normal  Created     9s         kubelet, worker-0 Created container nginx
  Normal  Started     9s         kubelet, worker-0 Started container nginx

您还可以使用命令kubectl get events观察集群中发生的所有事件。您可以添加-w选项,使命令等待新事件(您可以使用 Ctrl-C 终止命令):

$ kubectl get events -w
LAST SEEN   TYPE     REASON             OBJECT                     MESSAGE
<unknown>   Normal   Scheduled          pod/nginx-d   Successfully assigned default/nginx-d9bc977d8-h66wf to worker-0
4m20s       Normal   Pulling            pod/nginx-d   Pulling image "nginx"
4m19s       Normal   Pulled             pod/nginx-d   Successfully pulled image "nginx"
4m19s       Normal   Created            pod/nginx-d   Created container nginx
4m19s       Normal   Started            pod/nginx-d   Started container nginx
4m21s       Normal   Killing            pod/nginx-d   Stopping container nginx
4m21s       Normal   SuccessfulCreate   rs/nginx-d    Created pod: nginx-d9bc977d8-h66wf

在容器内部调试

可以使用命令kubectl exec从容器内部执行命令。这意味着容器包含调试工具。

下面的例子列出了一个nginx Pod 的单个容器的给定目录中的文件:

$ kubectl exec nginx-d9bc977d8-h66wf -- ls /usr/share/nginx/html
index.html

如果您想在容器内部运行一个交互式命令(一个 shell 或另一个与 TTY 交互的命令),您将需要指定--stdin--tty标志(您可以将它们缩写为-it):

$ kubectl exec -it nginx-d9bc977d8-h66wf -- bash
# ls /usr/share/nginx/html
index.html
[ other commands ]
# exit

调试服务

Kubernetes 的一个重要职责就是暴露使用ServiceIngress资源的 pod。

在本例中,您将运行两个nginx副本,并通过服务使它们可用:

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl scale deployment/nginx --replicas=2
deployment.apps/nginx scaled
$ kubectl expose deployment nginx --port=80
service/nginx exposed

如果一切顺利,服务应该公开这两个 pod(在这种情况下,对服务的请求在这两个 pod 之间进行负载均衡)。为了确保服务公开两个后端,您可以检查Endpoints资源:

$ kubectl get endpoints nginx
nginx   10.244.43.43:80,10.244.43.45:80 6s

该信息也可通过kubectl describe service命令获得:

$ kubectl describe service nginx
Name:              nginx
Namespace:         default
Labels:            app=nginx
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP:                10.103.237.61
Port:              <unset> 80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.43.43:80,10.244.43.45:80
Session Affinity:  None
Events:            <none>

这些命令显示该服务公开了两个 IP 地址。您可以使用命令验证这些地址是否与两个nginxpod 的地址相匹配(注意-o wide选项将显示更多信息,包括 pod 的 IP 地址)

$ kubectl get pods -l app=nginx -o wide
NAME                    READY   STATUS   RESTARTS   AGE     IP   [...]
nginx-f89759699-b7lsq   1/1     Running  0          1m16s   10.244.43.45 [...]
nginx-f89759699-l4f7c   1/1     Running  0          1m38s   10.244.43.43 [...]

记录

容器必须将日志输出到标准输出(stdout)或标准错误(stderr)流中,这样日志才能在 Kubernetes 基础设施中可用。

您可以使用kubectl logs命令显示某个特定 pod 的日志:

$ kubectl logs nginx
127.0.0.1 - - [01/Feb/2020:17:05:58 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0\
 " "-"

或者一组吊舱,由它们的标签选择(使用--prefix来区分吊舱):

$ kubectl logs -l app=nginx --prefix
[pod/nginx-86c57db685-cqm6c/nginx] 127.0.0.1 - - [01/Feb/2020:17:45:14 +0000] "GET /\
 HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
[pod/nginx-86c57db685-5f73p/nginx] 127.0.0.1 - - [01/Feb/2020:17:45:17 +0000] "GET /\
 HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"

logs命令的其他可用标志如下:

  • --follow(简称-f)允许跟随潮流;使用 Ctrl-C 停止。

  • --previous(简称为-p)允许查看以前的容器的日志,当一个容器崩溃并且你想查看导致它崩溃的错误时,这很有用。

  • --container=name(简称-c name)显示特定容器的日志,--all-containers显示所有容器的日志。

  • --timestamps在行首显示日志的时间戳。

节点级别的日志记录

默认情况下,Pod 的日志存储在运行 Pod 的节点中。当您使用kubeadm部署集群时,可以在/var/log/pods目录中找到日志。集群管理员负责为这些日志安装一些日志循环。

使用节点日志记录代理进行集群级日志记录

在节点级别收集的日志可以导出到外部日志后端(syslogStackDriverElastic等)。).fluentd ( www.fluentd.org )是一个日志代理,您可以将它部署在每个节点上,收集日志并将它们发送到日志后端。您可以在节点系统上使用DaemonSet或专用服务来部署fluentd

使用 Sidecar 将日志重定向到标准输出

如果您的应用不能将日志输出到stdoutstderr,而只能输出到文件,您可以运行一个 sidecar 容器来读取这些日志文件,并将它们传输到自己的stdout。通过这种方式,日志在节点级别变得可用,并且可以使用kubectl logs进行探索,并使用日志记录代理进行导出。

监视

您可以使用kubectl top命令来监控集群和 pod 的节点。

要使用这个命令,首先必须在集群上安装度量服务器。您可以按照第七章中的说明进行安装。

然后,您可以运行以下命令来获取每个节点和单元的 CPU 和内存使用情况:

$ kubectl top nodes
NAME         CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
controller   180m         18%    1391Mi          38%
worker-0     83m          8%     1294Mi          36%

$ kubectl top pods
NAME                    CPU(cores)   MEMORY(bytes)
nginx-d9bc977d8-2bf2g   0m           2Mi

度量服务器和kubectl top命令只为您提供一组有限的短期度量。建议安装完整的监控解决方案。

用 Prometheus 监控

Prometheus (prometheus.io)是 CNCF 推出的一套完整的监控和警报解决方案。Prometheus 系统主要由

  • 抓取和存储时间序列数据的服务器

  • 用于检测应用代码的客户端库

  • 用于导出主机指标的节点导出器

  • 处理警报的警报管理器

服务器是一个有状态的应用,它会定期提取节点和应用组件,以收集包含指标的时间序列数据,并将其保存到数据库中。

在 Prometheus 提供的客户端库的帮助下,应用开发人员负责将指标暴露给 monitor。指标通常在/metrics端点公开。

集群管理员负责在集群节点上部署节点导出器,这些导出器将自动公开主机的指标。

警报管理器用于根据一个或多个收集的指标创建和配置警报。

Grafana 仪表板( www.grafana.com )是 Prometheus 的伴侣,它将帮助您以图形方式展示和探索您的指标。

十五、升级集群

升级 Kubernetes 集群分两个阶段完成。首先升级控制面板节点,然后升级工作节点。可以升级到下一个次要版本或同一次要版本的任何其他下一个修补程序版本。例如,当您的群集使用版本 1.18.6 时,您可以升级到 1.18.p(其中 p >= 7)和 1.19.x(无论 x 的值如何),但不能升级到 1.20.x。

由于控制面板和工作器在主机系统上运行,您还需要升级这些系统。您将看到如何准备集群,以便在不中断应用的情况下进行这些操作。

最后,您将了解如何备份和恢复集群证书和数据。

升级控制器

首先,安装想要的 kubeadm 版本:

$ gcloud compute ssh controller
Welcome to controller
$ sudo apt update && apt-cache policy kubeadm
$ sudo apt update && \
   sudo apt-get install \
   -y --allow-change-held-packages \
   kubeadm=1.19.0-00
$ sudo apt-mark hold kubeadm
$ kubeadm version -o short
v1.19.0

排空控制器节点:

$ kubectl drain controller --ignore-daemonsets \
   --delete-local-data
node/controller evicted

检查可能的升级计划:

$ sudo kubeadm upgrade plan

[...]

Components that must be upgraded manually after you have upgraded the control plane \
with 'kubeadm upgrade apply':
COMPONENT   CURRENT       AVAILABLE
Kubelet     3 x v1.18.6   v1.19.0

Upgrade to the latest stable version

:

COMPONENT            CURRENT   AVAILABLE
API Server           v1.18.6   v1.19.0
Controller Manager   v1.18.6   v1.19.0
Scheduler            v1.18.6   v1.19.0
Kube Proxy           v1.18.6   v1.19.0
CoreDNS              1.6.7     1.6.7
Etcd                 3.4.3     3.4.3-0

You can now apply the upgrade by executing the following command:

        kubeadm upgrade apply v1.19.0

显示了几种可能性:一种是升级到系列的最新版本,另一种是升级到使用了kubeadm版本的最新稳定版本。

在前面的屏幕中,仅显示升级到最新稳定版本的计划。

让我们开始升级:

$ sudo kubeadm upgrade apply v1.19.0
[...]
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.19.0". Enjoy!

再次使节点可调度:

$ kubectl uncordon controller
node/controller uncordoned

升级控制器上的kubeletkubectl:

$ sudo apt-get update && \
   sudo apt-get install \
   -y --allow-change-held-packages \
   kubelet=1.19.0-00 kubectl=1.19.0-00
$ sudo apt-mark hold kubelet kubectl

此时,控制器节点应该显示最新版本:

$ kubectl get nodes
NAME         STATUS   ROLES   AGE   VERSION
controller   Ready    master  13d   v1.19.0
worker-0     Ready    <none>  13d   v1.18.6
worker-1     Ready    <none>  13d   v1.18.6

提升工人

对每个工人重复这些步骤。

首先,从您的计算机中清空节点:

$ kubectl drain worker-0 --ignore-daemonsets
node/worker-0 cordoned
node/worker-0  drained

然后,连接到节点并安装所需版本的 kubeadm:

$ gcloud compute ssh worker-0
Welcome to worker-0
$ sudo apt update && \
   sudo apt-get install \
   -y --allow-change-held-packages \
   kubeadm=1.19.0-00
$ sudo apt-mark hold kubeadm
$ kubeadm version -o short
v1.19.0

升级节点:

$ sudo kubeadm upgrade node
[...]
[upgrade] The configuration for this node was successfully updated!

升级节点上的kubeletkubectl:

$ sudo apt-get update && \
   sudo apt-get install \
   -y --allow-change-held-packages \
   kubelet=1.19.0-00 kubectl=1.19.0-00
$ sudo apt-mark hold kubelet kubectl

从您的计算机上,再次使节点可调度:

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

升级所有工作节点后,您应该在群集的每个节点上获得最新版本:

$ kubectl get nodes
NAME         STATUS   ROLES   AGE   VERSION
controller   Ready    master  13d   v1.19.0
worker-0     Ready    <none>  13d   v1.19.0
worker-1     Ready    <none>  13d   v1.19.0

升级操作系统

如果您需要重新启动集群节点的主机进行维护(例如,进行内核或硬件升级),您首先需要清空节点:

$ kubectl drain $NODENAME --ignore-daemonsets
node/nodename drained

排空节点将产生两种效果:

  • 从该节点中逐出所有单元,由副本集控制的所有单元将在另一个节点上重新安排。

  • 使此节点不可计划,以便在维护期间不会在此节点上计划新的 Pod。

现在,您可以安全地对操作系统或硬件进行维护操作。

维护结束后,您可以取消对节点的授权,使节点再次可调度:

$ kubectl uncordon $NODENAME
node/nodename uncordoned

备份群集

集群安装后备份文件/etc/kubernetes/pki/ca.crt/etc/kubernetes/pki/ca.key

使用以下命令定期创建etcd数据库的快照

$ kubectl exec -it -n kube-system etcd-controller \
   sh -- -c "ETCDCTL_API=3 etcdctl snapshot save snapshot.db \
   --cacert /etc/kubernetes/pki/etcd/server.crt \
   --cert /etc/kubernetes/pki/etcd/ca.crt \
   --key /etc/kubernetes/pki/etcd/ca.key"
Snapshot saved at snapshot.db
$ kubectl cp -n kube-system etcd-controller:snapshot.db snapshot.db

恢复集群

按照第一章中的步骤重新安装控制器,并在运行kubeadm init前停止。

将 ca.crt 和 ca.key 复制到/etc/kubernetes/pki 中,恢复良好的权限:

# ls -l /etc/kubernetes/pki/
total  8
-rw-r--r-- 1 root root 1025 Feb 1 10:43 ca.crt
-rw------- 1 root root 1675 Feb 1 10:43 ca.key

将 snapshot.db 放在/mnt 中,然后运行:

$ docker run --rm \
   -v '/mnt:/backup' \
   -v '/var/lib/etcd:/var/lib/etcd' \
   --env ETCDCTL_API=3 \
   'k8s.gcr.io/etcd-amd64:3.1.12' \
   /bin/sh -c \
   "etcdctl snapshot restore /backup/snapshot.db ; mv /default.etcd/member/ /var/lib/etcd/"

再次安装控制面板:

$ gcloud config set compute/zone us-west1-c # or your selected zone
Updated property [compute/zone].
$ KUBERNETES_PUBLIC_ADDRESS=$(gcloud compute instances describe controller \
  --zone $(gcloud config get-value compute/zone) \
  --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
$ sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --ignore-preflight-errors=NumCPU \
  --apiserver-cert-extra-sans=$KUBERNETES_PUBLIC_ADDRESS \
  --ignore-preflight-errors=DirAvailable--var-lib-etcd

十六、命令行工具

库布特雷

kubectl是用于 Kubernetes 集群的命令行工具。您可以使用它来创建应用资源和集群资源,与正在运行的容器进行交互,以及管理集群。

kubectl completion

  • 该命令输出要执行的 shell 代码,以使自动完成与kubectl命令一起工作。它最简单的用法是" source 它的输出,这将使自动完成对当前 shell 会话可用:

  • 或者,如果使用zsh外壳:

$ source <(kubectl completion bash)

  • 运行kubectl completion --help获取如何永久安装完井的说明。
$ source <(kubectl completion zsh)

管理 kubeconfig 文件

和其他 Kubernetes 程序在一个 kubeconfig 文件中获得连接到 Kubernetes 集群所需的凭证。默认情况下,在$HOME/.kube/config搜索该文件。可以通过使用--kubeconfig标志或定义KUBECONFIG环境变量来使用另一个文件。

KUBECONFIG的值是一个冒号分隔的配置文件路径列表,例如:

/etc/kubernetes/config:$HOME/.kube/config

当定义几个路径时,kubectl 将合并这些不同的文件(在内存中)并将结果作为一个单独的配置文件使用。

kubeconfig 文件由以下部分组成

  • 集群信息列表(证书、API URL)

  • 用户凭证列表

  • 上下文的列表,每个上下文引用现有的集群、用户和默认名称空间

  • 一个当前上下文,指向上下文列表中的特定上下文

kubectl config

  • get-clustersset-clusterdelete-cluster编辑集群信息。

  • set-credentials编辑用户凭证。

  • get-contextsset-contextdelete-contextrename-context编辑上下文。

  • setunset是编辑 kubeconfig 文件任何字段的通用子命令。

  • current-context获取当前上下文的名称。

  • use-context设置当前上下文的名称。

  • view查看命令可见的 kubeconfig 信息(取决于--kubeconfig标志和KUBECONFIG环境变量),特别有用的是查看合并几个配置文件的结果或用--minify获取当前上下文所需的信息。

  • 此命令提供子命令来编辑集群、用户和上下文的列表,以及切换当前上下文:

通用命令

kubectl apply

  • 大多数 kubectl 命令在命令式模式下使用。相反,apply命令用于声明性模式,用于应用 YAML/JSON 模板,即用于根据资源在 YAML 或 JSON 文件中的定义向集群 API 声明资源。

kubectl get

  • -o yaml标志获取资源列表或它们的完整定义。

kubectl delete

  • 删除一个或一组资源。

kubectl edit

  • 在您的首选编辑器中交互编辑资源定义(您可以更改EDITOR环境变量来更改它)。

kubectl create namespace

  • 创建新的命名空间。

创建应用资源

创建和参数化工作负载

kubectl run

  • 部署新的工作负载。这些命令的大多数形式都不推荐使用。

  • 不推荐使用的方法是使用单个容器部署 Pod:

  • 标志可用于设置容器的特定字段:映像拉取策略、环境变量、端口、资源请求和限制。

  • dry-run=client -o yaml有助于输出 Pod 的声明形式,稍后可以使用kubectl apply对其进行编辑和应用。

kubectl run podname --image=imagename

kubectl create deployment

  • 以最简单的形式创建部署:kubectl create deployment nginx --image=nginx。没有可用于参数化部署的标志。

  • dry-run=client -o yaml对于输出部署的声明形式很有用,可以在以后使用kubectl apply进行编辑和应用。

  • setscale命令对于参数化部署非常有用。

kubectl create cronjob

  • 给定映像和时间表,创建一个 cronjob。您还可以指定要传递给容器的命令和参数,这在使用像busybox这样的通用映像时很有用:

  • 使用man 5 crontab获取schedule标志的规格。

  • dry-run=client -o yaml对于输出 cronjob 的声明形式很有用,以后可以用kubectl apply编辑和应用它。

kubectl create cronjob pinghost --image=busybox \
--schedule="10 * * * *" \
-- sh -c 'ping -c 1 myhost'

kubectl create job

执行一项工作。有两种形式可用:

  • 给定一个映像和一个可选命令,创建一个作业(与create cronjob命令非常相似,但只针对一个作业):

  • 绕过 cronjob 的计划,从现有的 cronjob 创建作业:

kubectl create job pinghost --image=busybox \
-- sh -c 'ping -c 1 myhost'

kubectl create job pinghost --from=cronjob/pinghost

kubectl scale

  • 将适用的资源(部署、副本集、状态集)扩展到给定数量的副本。通过类型/名称、标签选择器或文件来选择要缩放的资源。可以指定一些前提条件:当前副本的数量和资源的版本。

kubectl autoscale

  • 为适用的资源(部署、复制集、状态集)创建自动缩放器。通过类型/名称或文件来选择要自动缩放的资源。重要的标志是minmaxcpu-percent,以指示副本数量的限制和资源将被扩展的平均 CPU 百分比。

kubectl rollout

  • 用于滚动更新和回滚适用资源(Deployment、DaemonSet、StatefulSet)的命令集。详见第五章“更新和回滚”一节。

配置工作负载

kubectl create configmap

创建一个ConfigMap资源,获取键/值对:

  • 从环境样式文件(--from-env-file):键和值将从文件中提取

  • From files ( --from-file):对于指定目录下的指定文件或所有文件,键将是文件名或指定键,值将是文件的内容:

# .env
key1=value1
key2=value2

  • 从文字值(--from-literal):
--from-file=dir --from-file=file1.txt --from-file=key=file2.txt

--from-literal=key1=value1 --from-literal=key2=value2

kubectl create secret

创建一个Secret资源。有三种形式可用:

  • create secret generic:非常类似于create configmap,从 env 样式的文件、文件和文字值中添加键/值对。

  • create secret docker-registry:创建一个用作imagePullSecrets的秘密(参见第十一章中的“使用私有 Docker 注册表”一节)。有两种形式可用,来自 Docker 配置文件(--from-file或通过指定注册表信息(--docker-server--docker-username--docker-password--docker-email)。

  • create secret tls :给定存储在文件中的公钥/私钥对(--cert--key),创建 TLS 秘密。

暴露 POD

kubectl create service

  • 创建一个Service资源。有四种形式可用,每种服务类型一种:

    • create service clusterip创建一个集群 IP 服务,用--tcp标志:

      kubectl create service clusterip my-svc \
      --tcp=8084:80
      
      

      指定端口

  • 将添加一个选择器app=my-svc,以匹配带有此标签的 pod。您可能需要对其进行编辑,以匹配所需窗格的标签。

  • 可以使用--clusterip=x.y.z.a为服务指定您自己的 IP,或者使用--clusterip=None创建一个无头服务。

    • create service nodeport创建节点端口服务。除了clusterip变量之外,还可以用--node-port标志:

      kubectl create service nodeport my-svc \
      --tcp=8084:80 --node-port=30080
      
      

      指定一个节点端口

    • create service loadbalancer创建一个负载均衡器服务,用--tcp标志:

      kubectl create service loadbalancer my-svc \
      --tcp=8084:80
      
      

      指定集群 IP 端口

  • 请注意,不可能为底层节点端口指定端口。

    • create service externalname创建外部名称服务:

      kubectl create service externalname my-svc \
      --external-name=an-svc.domain.com
      
      

kubectl expose

  • 公开一个适用的资源(服务、Pod、副本集、部署),创建一个新的Service资源。

批准

kubectl create role

kubectl create clusterrole

kubectl create rolebinding

kubectl create clusterrolebinding

kubectl create serviceaccount

kubectl auth

所有这些命令都用于创建和验证授权。详见第十一章“授权”一节。

注释和标签

kubectl annotate

  • 将元数据附加到任何类型的资源上,这些资源不是由 Kubernetes 使用,而是由工具或系统扩展使用。

kubectl label

  • 编辑任何种类资源的标签,用作选择器。

与应用交互

kubectl attach

  • 附加到已在现有容器中运行的进程,并显示其输出。

kubectl exec

  • 在现有容器中执行新命令。

kubectl cp

  • 将现有容器中的文件复制到本地计算机中。

源文件和目标文件的语法如下

  • /path/to/file对于本地计算机中的文件

  • pod:/path/to/filepod的容器中的文件

  • namespace/pod:/path/to/file对于namespacepod容器中的文件

-c container标志指示 Pod 的哪个特定容器是目标。

kubectl describe

  • 显示资源的详细信息。

kubectl logs

  • 打印 Pod 中容器的日志。

kubectl port-forward

  • 将本地端口转发到 Pod:

  • type可以是一个 Pod,一个管理 Pod 的工作负载(复制集、部署等。),或者公开 Pod 的服务。

  • --address ip标志指定监听的本地地址,默认为127.0.0.1

kubectl port-forward type/name local-port:remote-port

kubectl proxy

  • 在本地机器上运行 Kubernetes API 服务器的代理。

kubectl top

  • 显示节点或单元的资源(CPU/内存/存储)使用情况。

管理集群

kubectl taint

  • 编辑节点污点。

kubectl uncordon

  • 将节点标记为可调度。

kubectl cordon

  • 将节点标记为不可调度。

kubectl drain

  • 通过将节点标记为不可调度并从该节点中驱逐单元来准备维护节点。

kubectl certificate

  • 批准/拒绝证书签名请求。

kubectl cluster-info

  • 显示主服务器和服务的地址。dump子命令转储所有适合调试和诊断集群问题的信息。

kubectl version

  • 显示客户端和服务器的版本。

获取文档

kubectl api-versions

  • 显示服务器上支持的 API 版本。

kubectl api-resources

  • 显示服务器上支持的 API 资源。

kubectl explain

  • 资源的列表和文档字段。

kubectl options

  • 显示所有命令支持的通用标志。

kubectl help

  • 显示关于 kubectl 的内嵌帮助。

Helm

Helm 是 Kubernetes 的包装经理。

Helm charts 是帮助您定义、安装和升级 Kubernetes 应用的包,这些图表存储在 Helm 存储库中。

您可以使用helm命令来搜索、安装、升级、回滚和卸载图表。

在你的开发机器上安装头盔

Helm 由单个二进制组成,helm。您可以通过手动下载这个二进制文件或者使用系统的软件包管理器来安装它。以下是针对不同操作系统下载和安装二进制文件的说明。

您可以在 https://github.com/helm/helm/releases 获得最新发布的版本。

Linux 操作系统

$ curl -LO https://get.helm.sh/helm-v3.3.0-linux-amd64.tar.gz

$ tar zxvf helm-v3.3.0-linux-amd64.tar.gz
$ sudo mv ./linux-amd64/helm /usr/local/bin/helm
# Test it is working correctly
$ helm version --short
v3.3.0+g8a4aeec

苹果

$ curl -LO https://get.helm.sh/helm-v3.3.0-darwin-amd64.tar.gz

$ tar zxvf helm-v3.3.0-linux-amd64.tar.gz
$ sudo mv ./linux-amd64/helm /usr/local/bin/helm
# Test it is working correctly
$ helm version --short
v3.3.0+g8a4aeec

Windows 操作系统

$ url -LO https://get.helm.sh/helm-v3.3.0-windows-amd64.zip

# Unzip the downloaded file,
# Move the helm binary into your PATH,
$ helm version --short
v3.3.0+g8a4aeec

安装图表

图表存储在 Helm 存储库中。您可以使用 Helm Hub ( https://hub.helm.sh/ )来发现新的存储库。

helm repo命令帮助您管理您有权访问的存储库。安装后,您可以看到您无权访问任何存储库:

$ helm repo list
Error: no repositories to show

您可以使用命令helm repo add添加新的存储库,例如:

$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
$ helm repo list
NAME     URL
Bitnami  https://charts.bitnami.com/bitnami

现在,您可以从这个存储库中安装图表:

$ helm install my-wp-install bitnami/wordpress
[...]

稍后,当新版本发布时,您可以使用helm upgrade命令升级您的安装:

$ helm upgrade my-wp-install bitnami/wordpress
Release "my-wp-install" has been upgraded. Happy Helming!
[...]

您可以使用helm history管理已部署修订的历史记录:

$ helm history my-wp-install
REVISION  UPDATED                    STATUS       CHART              APP VERSION   DESCRIPTION
1         Sat Aug 10 10:39:04 2020   superseded   wordpress-9.4.2    .4.1          Install complete
2         Sat Aug 15 10:46:54 2020   deployed     wordpress-9.4.3  5.4.2         Upgrade complete

如有必要,您可以回滚到以前的版本:

$ helm rollback my-wp-install 1
Rollback was a success! Happy Helming!
$ helm history my-wp-install
REVISION  UPDATED                    STATUS       CHART            APP VERSION   DESCRIPTION
1         Sat Aug 10 10:39:04 2020   superseded   wordpress-9.4.2   5.4.1         Install complete
2         Sat Aug 15 10:46:54 2020   superseded   wordpress-9.4.3   5.4.2         Upgrade complete
3         Sat Aug 15 10:50:59 2020   deployed     wordpress-9.4.2   5.4.1         Rollback to 1

您可以使用helm uninstall命令卸载该软件包:

$ helm uninstall my-wp-install
release "my-wp-install" uninstalled

创建您自己的图表

命令helm create用于创建你自己的包。当您运行此命令时,它将使用默认应用为您创建一个新的目录结构:

$ helm create my-nginx
Creating my-nginx
$ tree my-nginx
my-nginx
├─ charts
├─ Chart.yaml
├─ templates
│  ├── deployment.yaml
│  ├── _helpers.tpl
│  ├── hpa.yaml
│  ├── ingress.yaml
│  ├── NOTES.txt
│  ├── serviceaccount.yaml
│  ├── service.yaml
│  └── tests
│       └── test-connection.yaml
└─ values.yaml

Chart.yaml文件包含关于包的元数据。

templates目录包含 Kubernetes 清单,这些清单将用于部署组成应用的不同资源(部署、服务、入口、服务帐户和水平 Pod 自动缩放器)。如果您查看其中一个文件,可以看到这些清单是模板化的。

values.yaml文件包含用于个性化最终清单的值,最终清单将由模板创建。

Helm 使用 Go 模板引擎,你可以在 https://helm.sh/docs/chart_template_guide/ 找到相关语言的所有细节。

您可以随意删除templates目录和values.yaml中的文件,创建自己的模板和值文件,或者如果您的应用与默认应用相似,可以编辑它们。

当您编辑完模板和值后,您可以使用命令helm package打包您的图表:

$ helm package my-nginx/
Successfully packaged chart and saved it to: my-nginx-0.1.0.tgz

该命令创建一个包含模板的归档文件。您可以通过 Helm 存储库分发该图表,也可以使用以下命令在本地使用它

$ helm install test-my-nginx ./my-nginx-0.1.0.tgz
[...]

定制

与基于模板的 Helm 不同,Kustomize 基于定制。

当您希望在有限的环境中重用清单来部署应用时,此工具非常有用。

为此,首先定义包含所有环境的公共库的 base 清单;然后,通过将补丁变形器应用于这些基本清单,创建覆盖来专门化每个环境的基本清单。

开发和生产环境示例

对于这种典型情况,我们将创建一个包含两种环境通用的基本清单的base目录,然后创建分别用于部署到开发和生产环境的devproduction目录。

我们将创建以下文件结构:

$ tree .
.
├─ base
│  ├─  deployment.yaml
│  ├─ kustomization.yaml
│  └─ service.yaml
├─ dev
│  ├─ kustomization.yaml
│  ├─ namespace.yaml
│  └─ resources-patch.yaml
└─ production
   ├─ kustomization.yaml
   ├─ namespace.yaml
   └─ resources-patch.yaml

// base/deployment.yaml
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

// base/service.yaml
apiVersion: v1
kind:  Service
metadata:
  labels:
    app: nginx
  name: nginx

spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: ClusterIP

// base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml

base目录中,您可以使用以下命令:

$ cd base
$ kubectl apply -k .
service/nginx created
deployment.apps/nginx created
# clean
$ kubectl delete -k .

当使用带有-k标志的kubectl apply时,该命令将在命令行中指定的目录(这里是当前目录)中查找一个kustomization.yaml文件,并应用它。

一个kustomization.yaml文件包含不同类型的指令:

  • 指向清单文件的resources指令

  • 将指定补丁应用到这些清单的补丁指令

  • 修饰符指令将以一些特定的方式修改这些清单。

因此,当kubectl 将包含kustomization.yaml文件的目录应用于时,它将首先加载资源,然后修补和修改它们,最后像kubectl apply -f命令一样应用结果清单。

回到我们的例子,kustomization.yaml文件只包含一个resources指令;这相当于应用了两个文件deployment.yamlservice.yaml

我们现在将为开发环境创建一个覆盖。以下是 kustomization 和相关文件:

// dev/kustomization.yaml
bases:
- ../base/

resources:
- namespace.yaml

namespace: dev

commonLabels:
  env: dev

patchesStrategicMerge:
- resources-patch.yaml

// dev/namespace.yaml
kind: Namespace
metadata:
  name: dev

// dev/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    spec:
      containers:
      - name: nginx
        resources:
          requests:
            cpu: 100m
            memory: 100Mi

kustomization.yaml文件中,bases指令表明这个覆盖图是基于../base目录中的 kustomization 文件。

然后包含一些新的资源:这里是创建dev名称空间的清单。

接下来,应用一些修饰符:namespace将修改每个资源清单,将metadata.namespace值设置为devcommonLabels将为每个资源清单添加env: dev标签。

最后,将应用在resources-patch.yaml中找到的补丁,将资源请求添加到部署中。

其他一些修改器也是可用的,包括:

  • namePrefixnameSuffix将用给定的前缀或后缀修改每个资源清单的metadata.name

  • commonAnnotations将为每个资源清单添加注释。

  • images是旧/新映像名称的映射,它将更改清单中的容器映像名称的值。

  • replicas将改变每个相关资源清单的副本数量。

作为一个练习,您可以使用生产环境的特定修补程序和修改器来创建生产环境的覆盖。

滚动配置更新

两个指令configMapGeneratorsecretGenerator用于从kustomization.yaml文件生成配置图和密码。

当这些生成器创建 ConfigMap 或 Secret 时,它们会在其名称中添加一个随机后缀,并且从容器到该资源的引用会被修改为包含该后缀。这样,当您更新生成的配置映射或机密时,会创建一个新的配置映射或机密,更有趣的是,引用此资源的部署会被更新以更改配置映射/机密名称。将通过滚动更新推出新的 pod 来应用这一新配置。

例如,以下是 nginx 容器的清单,该容器从配置映射中提供静态文件:

// deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      volumes:
      - name: static-files
        configMap:
          name: static-files
      containers:
      - image: nginx
        name: nginx
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: static-files

<!-- index.html -->
version 1

// kustomization.yaml
resources:
- deployment.yaml

configMapGenerator:
- name: static-files
  files:
  - index.html

让我们用 Kustomize 来应用这些清单:

$ kubectl apply -k .
configmap/static-files-ck2b6hc6th created
deployment.apps/nginx created

当我们在configMapGenerator指令中将其命名为static-files时,我们可以看到创建的配置图被命名为static-files-ck2b6hc6th。此外,我们可以看到部署的 PodTemplate 中引用的配置图也是static-files-ck2b6hc6th,而不是指定的static-files:

$ kubectl get deployments.apps nginx -o jsonpath='{.spec.template.spec.volumes[0].configMap.name}'
static-files-ck2b6hc6th

现在,编辑 index.html 文件,然后再次应用kustomization.yaml文件:

$ echo 'version 2' > index.html
$ kubectl apply -k .
configmap/static-files-bg9hg69mh9 created
deployment.apps/nginx configured

$ kubectl get deployments.apps nginx -o jsonpath='{.spec.template.spec.volumes[0].configMap.name}'
static-files-bg9hg69mh9

$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

您可以看到一个名为static-files-bg9hg69mh9的新配置图已经创建,部署已经更新以引用这个新配置图,并且出现了部署的首次展示。

请注意,您可以通过使用disableNameSuffixHash选项来禁用此行为:

[...]
configMapGenerator:
- name: static-files
  files:
  - index.html
  options:
    disableNameSuffixHash: true

posted @ 2024-08-12 11:18  绝不原创的飞龙  阅读(44)  评论(0编辑  收藏  举报