静态 Pod
在 Kubernetes 集群中除了我们经常使用到的普通的 Pod 外,还有一种特殊的 Pod,叫做Static Pod,也就是我们说的静态 Pod,静态 Pod 有什么特殊的地方呢?
静态 Pod 直接由节点上的 kubelet 进程来管理,不通过 master 节点上的 apiserver。无法与我们常用的控制器 Deployment 或者 DaemonSet 进行关联,它由 kubelet 进程自己来监控,当 pod 崩溃时会重启该 pod,kubelet 也无法对他们进行健康检查。静态 pod 始终绑定在某一个 kubelet 上,并且始终运行在同一个节点上。kubelet 会自动为每一个静态 pod 在 Kubernetes 的 apiserver 上创建一个镜像 Pod,因此我们可以在 apiserver 中查询到该 pod,但是不能通过 apiserver 进行控制(例如不能删除)。
创建静态 Pod 有两种方式:配置文件和 HTTP 两种方式
配置文件
配置文件就是放在特定目录下的标准的 JSON 或 YAML 格式的 pod 定义文件。用 kubelet --pod-manifest-path=<the directory>
来启动 kubelet 进程,kubelet 定期的去扫描这个目录,根据这个目录下出现或消失的 YAML/JSON 文件来创建或删除静态 pod。
比如我们在 node01 这个节点上用静态 pod 的方式来启动一个 nginx 的服务。我们登录到node01节点上面,可以通过下面命令找到 kubelet 对应的启动配置文件
$ systemctl status kubelet
配置文件路径为:
$ vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
......
Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true"
打开这个文件我们可以看到其中有一个参数--pod-manifest-path
的配置, 所以如果我们通过kubeadm 的方式来安装的集群环境,对应的 kubelet 已经配置了我们的静态 Pod 文件的路径,默认地址为 /etc/kubernetes/manifests,所以我们只需要在该目录下面创建一个标准的 Pod 的 JSON 或者 YAML 文件即可,如果你的 kubelet 启动参数中没有配置上面的--pod-manifest-path
参数的话,那么添加上这个参数然后重启 kubelet 即可:
[root@ node01 ~] $ cat <<EOF >/etc/kubernetes/manifest/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
app: static
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
EOF
通过 HTTP 创建静态 Pods
kubelet 周期地从–-manifest-url=
参数指定的地址下载文件,并且把它翻译成 JSON/YAML 格式的 pod 定义。此后的操作方式与-–pod-manifest-path=
相同,kubelet 会不时地重新下载该文件,当文件变化时对应地终止或启动静态 pod。
kubelet 启动时,由--pod-manifest-path=
或 --manifest-url=
参数指定的目录下定义的所有 pod 都会自动创建,例如,我们示例中的 static-web。(可能要花些时间拉取nginx 镜像,耐心等待…)
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f6d05272b57e nginx:latest "nginx" 8 minutes ago Up 8 minutes k8s_web.6f802af4_static-web-fk-node1_default_67e24ed9466ba55986d120c867395f3c_378e5f3c
现在我们通过kubectl工具可以看到这里创建了一个新的镜像 Pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
static-web-my-node01 1/1 Running 0 2m
静态 pod 的标签会传递给镜像 Pod,可以用来过滤或筛选。 需要注意的是,我们不能通过 API 服务器来删除静态 pod(例如,通过kubectl命令),kubelet 不会删除它。
$ kubectl delete pod static-web-my-node01
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
static-web-my-node01 1/1 Running 0 12s
我们尝试手动终止容器,可以看到kubelet很快就会自动重启容器。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED ...
5b920cbaf8b1 nginx:latest "nginx -g 'daemon of 2 seconds ago ...
静态 Pod 的动态增加和删除
运行中的 kubelet 周期扫描配置的目录(我们这个例子中就是 /etc/kubernetes/manifests)下文件的变化,当这个目录中有文件出现或消失时创建或删除 pods:
$ mv /etc/kubernetes/manifests/static-web.yaml /tmp
$ sleep 20
$ docker ps
// no nginx container is running
$ mv /tmp/static-web.yaml /etc/kubernetes/manifests
$ sleep 20
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED ...
e7a62e3427f1 nginx:latest "nginx -g 'daemon of 27 seconds ago
其实我们用 kubeadm 安装的集群,master 节点上面的几个重要组件都是用静态 Pod 的方式运行的,我们登录到 master 节点上查看/etc/kubernetes/manifests目录:
$ ls /etc/kubernetes/manifests/
etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
现在明白了吧,这种方式也为我们将集群的一些组件容器化提供了可能,因为这些 Pod 都不会受到 apiserver 的控制,不然我们这里kube-apiserver怎么自己去控制自己呢?万一不小心把这个 Pod 删掉了呢?所以只能有kubelet自己来进行控制,这就是我们所说的静态 Pod。
Downward API
前面我们使用过一个 volumes 的属性,表示声明一个数据卷,我们可以通过命令kubectl explain pod.spec.volumes去查看该对象下面的属性非常多,前面我们只是简单使用了 hostPath 和 emptyDir{} 这两种模式,其中还有一种模式叫做downwardAPI,这个模式和其他模式不一样的地方在于它不是为了存放容器的数据也不是用来进行容器和宿主机的数据交换的,而是让 Pod 里的容器能够直接获取到这个 Pod 对象本身的一些信息。
# kubectl explain pod.spec.volumes
KIND: Pod
VERSION: v1
RESOURCE: volumes <[]Object>
DESCRIPTION:
List of volumes that can be mounted by containers belonging to the pod.
More info: https://kubernetes.io/docs/concepts/storage/volumes
Volume represents a named volume in a pod that may be accessed by any
container in the pod.
FIELDS:
awsElasticBlockStore <Object>
AWSElasticBlockStore represents an AWS Disk resource that is attached to a
kubelet's host machine and then exposed to the pod. More info:
https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore
azureDisk <Object>
AzureDisk represents an Azure Data Disk mount on the host and bind mount to
the pod.
azureFile <Object>
AzureFile represents an Azure File Service mount on the host and bind mount
to the pod.
cephfs <Object>
CephFS represents a Ceph FS mount on the host that shares a pod's lifetime
cinder <Object>
Cinder represents a cinder volume attached and mounted on kubelets host
machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md
configMap <Object>
ConfigMap represents a configMap that should populate this volume
csi <Object>
CSI (Container Storage Interface) represents ephemeral storage that is
handled by certain external CSI drivers (Beta feature).
downwardAPI <Object>
DownwardAPI represents downward API about the pod that should populate this
volume
emptyDir <Object>
EmptyDir represents a temporary directory that shares a pod's lifetime.
More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir
ephemeral <Object>
Ephemeral represents a volume that is handled by a cluster storage driver
(Alpha feature). The volume's lifecycle is tied to the pod that defines it
- it will be created before the pod starts, and deleted when the pod is
removed.
Use this if: a) the volume is only needed while the pod runs, b) features
of normal volumes like restoring from snapshot or capacity tracking are
needed, c) the storage driver is specified through a storage class, and d)
the storage driver supports dynamic volume provisioning through a
PersistentVolumeClaim (see EphemeralVolumeSource for more information on
the connection between this volume type and PersistentVolumeClaim).
Use PersistentVolumeClaim or one of the vendor-specific APIs for volumes
that persist for longer than the lifecycle of an individual pod.
Use CSI for light-weight local ephemeral volumes if the CSI driver is meant
to be used that way - see the documentation of the driver for more
information.
A pod can use both types of ephemeral volumes and persistent volumes at the
same time.
fc <Object>
FC represents a Fibre Channel resource that is attached to a kubelet's host
machine and then exposed to the pod.
flexVolume <Object>
FlexVolume represents a generic volume resource that is
provisioned/attached using an exec based plugin.
flocker <Object>
Flocker represents a Flocker volume attached to a kubelet's host machine.
This depends on the Flocker control service being running
gcePersistentDisk <Object>
GCEPersistentDisk represents a GCE Disk resource that is attached to a
kubelet's host machine and then exposed to the pod. More info:
https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk
gitRepo <Object>
GitRepo represents a git repository at a particular revision. DEPRECATED:
GitRepo is deprecated. To provision a container with a git repo, mount an
EmptyDir into an InitContainer that clones the repo using git, then mount
the EmptyDir into the Pod's container.
glusterfs <Object>
Glusterfs represents a Glusterfs mount on the host that shares a pod's
lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md
hostPath <Object>
HostPath represents a pre-existing file or directory on the host machine
that is directly exposed to the container. This is generally used for
system agents or other privileged things that are allowed to see the host
machine. Most containers will NOT need this. More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath
iscsi <Object>
ISCSI represents an ISCSI Disk resource that is attached to a kubelet's
host machine and then exposed to the pod. More info:
https://examples.k8s.io/volumes/iscsi/README.md
name <string> -required-
Volume's name. Must be a DNS_LABEL and unique within the pod. More info:
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
nfs <Object>
NFS represents an NFS mount on the host that shares a pod's lifetime More
info: https://kubernetes.io/docs/concepts/storage/volumes#nfs
persistentVolumeClaim <Object>
PersistentVolumeClaimVolumeSource represents a reference to a
PersistentVolumeClaim in the same namespace. More info:
https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims
photonPersistentDisk <Object>
PhotonPersistentDisk represents a PhotonController persistent disk attached
and mounted on kubelets host machine
portworxVolume <Object>
PortworxVolume represents a portworx volume attached and mounted on
kubelets host machine
projected <Object>
Items for all in one resources secrets, configmaps, and downward API
quobyte <Object>
Quobyte represents a Quobyte mount on the host that shares a pod's lifetime
rbd <Object>
RBD represents a Rados Block Device mount on the host that shares a pod's
lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md
scaleIO <Object>
ScaleIO represents a ScaleIO persistent volume attached and mounted on
Kubernetes nodes.
secret <Object>
Secret represents a secret that should populate this volume. More info:
https://kubernetes.io/docs/concepts/storage/volumes#secret
storageos <Object>
StorageOS represents a StorageOS volume attached and mounted on Kubernetes
nodes.
vsphereVolume <Object>
VsphereVolume represents a vSphere volume attached and mounted on kubelets
host machine
目前 Downward API 提供了两种方式用于将 Pod 的信息注入到容器内部:
- 环境变量:用于单个变量,可以将 Pod 信息和容器信息直接注入容器内部
- Volume 挂载:将 Pod 信息生成为文件,直接挂载到容器内部中去
环境变量
通过 Downward API 来将 Pod 的 IP、名称以及所对应的 namespace 注入到容器的环境变量中去,然后在容器中打印全部的环境变量来进行验证,对应资源清单文件如下:(env-pod.yaml)
apiVersion: v1
kind: Pod
metadata:
name: env-pod
namespace: kube-system
spec:
containers:
- name: env-pod
image: busybox
command: ["/bin/sh", "-c", "env"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
可以看到上面我们使用了一种新的方式来设置 env 的值:valueFrom,由于 Pod 的 name 和 namespace 属于元数据,是在 Pod 创建之前就已经定下来了的,所以我们可以使用 metata 就可以获取到了,但是对于 Pod 的 IP 则不一样,因为我们知道 Pod IP 是不固定的,Pod 重建了就变了,它属于状态数据,所以我们使用 status 这个属性去获取。另外除了使用 fieldRef获取 Pod 的基本信息外,还可以通过 resourceFieldRef 去获取容器的资源请求和资源限制信息。
我们直接创建上面的 Pod:
$ kubectl create -f env-pod.yaml
pod "env-pod" created
Pod 创建成功后,我们可以查看日志:
$ kubectl logs env-pod -n kube-system |grep POD
kubectl logs -f env-pod -n kube-system
POD_IP=10.244.1.121
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBE_DNS_SERVICE_PORT_DNS_TCP=53
HOSTNAME=env-pod
SHLVL=1
HOME=/root
KUBE_DNS_SERVICE_HOST=10.96.0.10
KUBE_DNS_PORT_9153_TCP_ADDR=10.96.0.10
KUBE_DNS_PORT_9153_TCP_PORT=9153
KUBE_DNS_PORT_9153_TCP_PROTO=tcp
KUBE_DNS_SERVICE_PORT=53
KUBE_DNS_PORT=udp://10.96.0.10:53
POD_NAME=env-pod
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBE_DNS_PORT_53_TCP_ADDR=10.96.0.10
KUBERNETES_PORT_443_TCP_PORT=443
KUBE_DNS_SERVICE_PORT_METRICS=9153
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBE_DNS_PORT_9153_TCP=tcp://10.96.0.10:9153
KUBE_DNS_PORT_53_UDP_ADDR=10.96.0.10
KUBE_DNS_PORT_53_TCP_PORT=53
KUBE_DNS_PORT_53_TCP_PROTO=tcp
KUBE_DNS_PORT_53_UDP_PORT=53
KUBE_DNS_SERVICE_PORT_DNS=53
KUBE_DNS_PORT_53_UDP_PROTO=udp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
POD_NAMESPACE=kube-system
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
KUBE_DNS_PORT_53_TCP=tcp://10.96.0.10:53
KUBE_DNS_PORT_53_UDP=udp://10.96.0.10:53
我们可以看到 Pod 的 IP、NAME、NAMESPACE 都通过环境变量打印出来了。
上面打印 Pod 的环境变量可以看到有很多内置的变量,其中大部分是系统自动为我们添加的,Kubernetes 会把当前命名空间下面的 Service 信息通过环境变量的形式注入到 Pod 中去:
$ kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 4d21h
Volume 挂载
Downward API除了提供环境变量的方式外,还提供了通过 Volume 挂载的方式去获取 Pod 的基本信息。接下来我们通过Downward API将 Pod 的 Label、Annotation 等信息通过 Volume 挂载到容器的某个文件中去,然后在容器中打印出该文件的值来验证,对应的资源清单文件如下所示:(volume-pod.yaml)
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
namespace: kube-system
labels:
k8s-app: test-volume
node-env: test
annotations:
own: youdianzhishi
build: test
spec:
volumes:
- name: podinfo
downwardAPI:
items:
- path: labels
fieldRef:
fieldPath: metadata.labels
- path: annotations
fieldRef:
fieldPath: metadata.annotations
containers:
- name: volume-pod
image: busybox
args:
- sleep
- "3600"
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
我们将元数据 labels 和 annotaions 以文件的形式挂载到了 /etc/podinfo 目录下,创建上面的 Pod:
$ kubectl create -f volume-pod.yaml
pod "volume-pod" created
创建成功后,我们可以进入到容器中查看元信息是不是已经存入到文件中了:
$ kubectl exec -it volume-pod /bin/sh -n kube-system
/ # ls /etc/podinfo/
..2019_11_13_09_57_15.990445016/ annotations
..data/ labels
/ # cat /etc/podinfo/labels
k8s-app="test-volume"
/ # cat /etc/podinfo/annotations
build="test"
kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"build\":\"test\",\"own\":\"youdianzhishi\"},\"labels\":{\"k8s-app\":\"test-volume\",\"node-env\":\"test\"},\"name\":\"volume-pod\",\"namespace\":\"kube-system\"},\"spec\":{\"containers\":[{\"args\":[\"sleep\",\"3600\"],\"image\":\"busybox\",\"name\":\"volume-pod\",\"volumeMounts\":[{\"mountPath\":\"/etc/podinfo\",\"name\":\"podinfo\"}]}],\"volumes\":[{\"downwardAPI\":{\"items\":[{\"fieldRef\":{\"fieldPath\":\"metadata.labels\"},\"path\":\"labels\"},{\"fieldRef\":{\"fieldPath\":\"metadata.annotations\"},\"path\":\"annotations\"}]},\"name\":\"podinfo\"}]}}\n"
kubernetes.io/config.seen="2019-11-13T17:57:15.320894744+08:00"
kubernetes.io/config.source="api"
我们可以看到 Pod 的 Labels 和 Annotations 信息都被挂载到 /etc/podinfo 目录下面的 lables 和 annotations 文件了。
目前,Downward API 支持的字段已经非常丰富了,比如:
1. 使用 fieldRef 可以声明使用:
spec.nodeName - 宿主机名字
status.hostIP - 宿主机IP
metadata.name - Pod的名字
metadata.namespace - Pod的Namespace
status.podIP - Pod的IP
spec.serviceAccountName - Pod的Service Account的名字
metadata.uid - Pod的UID
metadata.labels['<KEY>'] - 指定<KEY>的Label值
metadata.annotations['<KEY>'] - 指定<KEY>的Annotation值
metadata.labels - Pod的所有Label
metadata.annotations - Pod的所有Annotation
2. 使用 resourceFieldRef 可以声明使用:
容器的 CPU limit
容器的 CPU request
容器的 memory limit
容器的 memory request
需要注意的是,Downward API 能够获取到的信息,一定是 Pod 里的容器进程启动之前就能够确定下来的信息。而如果你想要获取 Pod 容器运行后才会出现的信息,比如,容器进程的 PID,那就肯定不能使用 Downward API 了,而应该考虑在 Pod 里定义一个 sidecar 容器来获取了。
在实际应用中,如果你的应用有获取 Pod 的基本信息的需求,一般我们就可以利用Downward API来获取基本信息,然后编写一个启动脚本或者利用initContainer将 Pod 的信息注入到我们容器中去,然后在我们自己的应用中就可以正常的处理相关逻辑了。
除了通过 Downward API 可以获取到 Pod 本身的信息之外,其实我们还可以通过映射其他资源对象来获取对应的信息,比如 Secret、ConfigMap 资源对象,同样我们可以通过环境变量和挂载 Volume 的方式来获取他们的信息,但是,通过环境变量获取这些信息的方式,不具备自动更新的能力。所以,一般情况下,都建议使用 Volume 文件的方式获取这些信息,因为通过 Volume 的方式挂载的文件在 Pod 中会进行热更新。
PodPreset
我们已经学习了很多 Pod 的知识点,但是可能有部分同学还是觉得 Pod 的字段属性太多了,很多记不住,查询文档效率不高,你是不是希望 Kubernetes 能够提供一个功能为 Pod 自动填充一些字段呢?这个需求还是很实际的,比如我们按照命名空间来划分不同的环境,然后我们在不同的环境上部署 Pod 后自动为我们加上环境相关的 Labels、Annotations 等等信息,这样就大大提高了编写 YAML 的效率,为此,在 Kubernetes v1.11 版本后就提供了一个叫做 PodPreset(Pod 预设值)的功能可以来解决这个问题。
Kubernetes 提供了一个 PodPreset 准入控制器,当启用后,PodPreset 会将应用创建请求传入到该控制器上。当有 Pod 创建请求发生时,系统将执行以下操作:
- 检索所有可用的 PodPreset
- 检查有 PodPreset 的标签选择器上的标签与正在创建的 Pod 上的标签是否匹配
- 尝试将由 PodPreset 定义的各种资源合并到正在创建的 Pod 中
- 出现错误时,在该 Pod 上引发记录合并错误的事件,PodPreset 不会注入任何资源到创建的 Pod 中
- 注释刚生成的修改过的 Pod spec,以表明它已被 PodPreset 修改过。注释的格式为 podpreset.admission.kubernetes.io/podpreset-
": " "
每个 Pod 可以匹配零个或多个 PodPrestet;并且每个 PodPreset 可以应用于零个或多个 Pod。 PodPreset 应用于一个或多个 Pod 时,Kubernetes 会修改 Pod Spec。对于 Env、EnvFrom 和 VolumeMounts 的更改,Kubernetes 修改 Pod 中所有容器的容器 spec;对于 Volume 的更改,Kubernetes 修改 Pod Spec。
可能在某些情况下,您希望你的 Pod 不会被任何 Pod Preset 所改变。在这些情况下,您可以在 Pod 的 Pod Spec 中添加上这样一个 annotation:podpreset.admission.kubernetes.io/exclude:"true"。
启用 PodPreset
要启用PodPreset功能,需要确保你使用的是 kubernetes 1.8版本以上,然后需要在准入控制中加入PodPreset,另外为了定义 PodPreset 对象,还需要其中 PodPreset 的 API 版本,在 APIServer 启动参数中添加如下配置:
- --enable-admission-plugins=NodeRestriction,PodPreset
- --runtime-config=settings.k8s.io/v1alpha1=true
我们这里是使用的 kubeadm 搭建的集群,所以只需要修改静态 Pod 配置即可,路径 /etc/kubernetes/manifests/kube-apiserver.yaml
,修改完成后,将 kube-apiserver.yaml 文件移除 manifests 目录,然后移动回来,相当于强制重启,执行如下命令:
$ kubectl api-versions |grep settings
settings.k8s.io/v1alpha1
$ kubectl get podpreset
No resources found in default namespace.
出现如上的提示证明已经开启成功。
示例
我们经常有一个需求就是需要同步 Pod 和宿主机的时间,一般情况下,我们是通过挂载宿主机的 localtime 来完成的,如下 Pod:(time-demo.yaml)
apiVersion: apps/v1
kind: Pod
metadata:
name: time-demo
labels:
app: time
spec:
containers:
- name: time-demo
image: nginx
ports:
- containerPort: 80
我们直接创建该 Pod:
$ kubectl apply -f time-demo.yaml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
time-demo 1/1 Running 0 2m8s
创建完成后,我们可以对比下节点的时间和 Pod 的时间:
$ date
Thu Nov 14 12:08:27 CST 2019
$ kubectl exec time-demo date
Thu Nov 14 04:08:41 UTC 2019
我们可以看到 Pod 的时间是 UTC 时间,和节点不同步,这个时候我们可以挂载宿主机的 localtime 文件到 Pod 中去,如下所示:
apiVersion: apps/v1
kind: Pod
metadata:
name: time-demo
labels:
app: time
spec:
volumes:
- name: host-time
hostPath:
path: /etc/localtime
containers:
- name: time-demo
image: nginx
volumeMounts:
- name: host-time
mountPath: /etc/localtime
ports:
- containerPort: 80
这个时候我们重新来测试下:
$ kubectl delete -f time-demo.yaml
$ kubectl apply -f time-demo.yaml
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
time-demo 1/1 Running 0 2m8s
这个时候我们再来看下时间是否同步了:
$ date
Thu Nov 14 12:13:40 CST 2019
$ kubectl exec time-demo date
Thu Nov 14 12:13:45 CST 2019
可以看到已经生效了。但是往往我们所有的 Pod 都有时间同步的需求,如果所有的 Pod 都来手动进行挂载,是不是显得繁琐多余啊,这个时候我们就可以利用 PodPreset 来预设模板,定义一个如下所示的 PodPreset 资源对象:(time-preset.yaml)
apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
name: time-preset
namespace: default
spec:
selector:
matchLabels:
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /etc/localtime
直接创建上面的资源对象:
$ kubectl apply -f time-preset.yaml
podpreset.settings.k8s.io/time-preset created
$ kubectl get podpreset
NAME CREATED AT
time-preset 2019-11-14T06:52:11Z
这个时候我们再来直接创建上面的 Pod:(time-demo.yaml)
apiVersion: apps/v1
kind: Pod
metadata:
name: time-demo
labels:
app: time
spec:
containers:
- name: time-demo
image: nginx
ports:
- containerPort: 80
这个 Pod 我们没有主动挂载时间文件,直接创建:
$ kubectl delete -f time-demo.yaml
$ kubectl apply -f time-demo.yaml
$ kubectl get pods -l app=time
NAME READY STATUS RESTARTS AGE
time-demo 1/1 Running 0 60s
这个时候验证下 Pod 时间是否和宿主机一致:
$ date
Thu Nov 14 14:55:02 CST 2019
$ kubectl exec time-demo date
Thu Nov 14 14:55:21 CST 2019
可以看到已经同步了~,可以直接导出 Pod 的资源清单查看:
$ kubectl get pod time-demo -o yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"labels":{"app":"time"},"name":"time-demo","namespace":"default"},"spec":{"containers":[{"image":"nginx","name":"time-demo","ports":[{"containerPort":80}]}]}}
podpreset.admission.kubernetes.io/podpreset-time-preset: "1469811"
creationTimestamp: "2019-11-14T06:54:06Z"
labels:
app: time
name: time-demo
namespace: default
resourceVersion: "1470398"
selfLink: /api/v1/namespaces/default/pods/time-demo
uid: 37a7ab94-c2c3-4b10-88df-0c486fb8d422
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: time-demo
ports:
- containerPort: 80
protocol: TCP
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/localtime
name: localtime
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-5tsh4
readOnly: true
dnsPolicy: ClusterFirst
enableServiceLinks: true
nodeName: ydzs-node3
priority: 0
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: default
serviceAccountName: default
terminationGracePeriodSeconds: 30
tolerations:
- effect: NoExecute
key: node.kubernetes.io/not-ready
operator: Exists
tolerationSeconds: 300
- effect: NoExecute
key: node.kubernetes.io/unreachable
operator: Exists
tolerationSeconds: 300
volumes:
- hostPath:
path: /etc/localtime
type: ""
name: localtime
- name: default-token-5tsh4
secret:
defaultMode: 420
secretName: default-token-5tsh4
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2019-11-14T06:54:06Z"
status: "True"
type: Initialized
- lastProbeTime: null
lastTransitionTime: "2019-11-14T06:54:22Z"
status: "True"
type: Ready
- lastProbeTime: null
lastTransitionTime: "2019-11-14T06:54:22Z"
status: "True"
type: ContainersReady
- lastProbeTime: null
lastTransitionTime: "2019-11-14T06:54:06Z"
status: "True"
type: PodScheduled
containerStatuses:
- containerID: docker://a774e82d4ebfde98c37b675f1a46377ccf01c0bba931507172309616dbf49165
image: nginx:latest
imageID: docker-pullable://nginx@sha256:922c815aa4df050d4df476e92daed4231f466acc8ee90e0e774951b0fd7195a4
lastState: {}
name: time-demo
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2019-11-14T06:54:21Z"
hostIP: 10.151.30.57
phase: Running
podIP: 10.244.3.64
podIPs:
- ip: 10.244.3.64
qosClass: BestEffort
startTime: "2019-11-14T06:54:06Z"
从上面 YAML 中也可以看出 Pod 被自动注入了 PodPreset 声明的模板,而且该命名空间下面的所有 Pod 默认都会被注入该模板。
需要注意的是 PodPreset 资源对象是 namespace scope 的,就是不同的命名空间需要创建不同的 PodPreset。