第一篇我们带着问题学习了Kubernetes的总体架构,了解了K8S的各个组件以及它们的作用,这篇文章将继续带着问题学习K8S中最为重要的概念POD,K8S中为什么引入POD而不是直接管理Docker,POD又是如何管理容器的,K8S是如何管理POD的,POD的生命周期等一系列问题。
问题一:Kubernetes为什么将POD作为最小的可部署单元,而不是一个单一的容器?
虽然直接部署单个容器似乎比较简单,但是有充分的理由需要添加一个由POD表示的抽象层。容器是一个实体,它指一个特定的事物。这个具体的事物可能Docker容器,但它也可能是一个rkt的容器,或由virtlet管理的虚拟机。每一种都有不同的要求。
更重要的是,Kubernetes需要更多的信息来管理一个容器,如重新启动策略,它定义了当容器终止时,或活性的探针时,进行哪些动作;从应用程序的角度,它定义了检测动作,判断容器进程是否还活着,如Web服务器响应HTTP请求。
区别于在现有的“实体”上超载附加属性,Kubernetes决定使用一个新的实体,POD。POD可以被认为是逻辑容器,包含一个或多个容器(包装),作为一个单一的实体被管理。
问题二:Kubernetes为什么让一个POD里放置多个容器?
POD中的容器运行在“逻辑主机”上;它们使用相同的网络namespace(换句话说,相同的IP地址和端口空间),以及相同的IPC名称空间。它们还可以使用共享卷。这些属性使得这些容器能够有效地通信,确保数据本地化。此外,POD使您可以以一个单元的模式管理几个紧密耦合的应用程序容器。
如果应用程序需要在同一主机上运行多个容器,为什么不将单个容器与所需的所有内容组合起来呢?首先,这样做很可能违反“一个容器一个进程”的原则。这很重要,因为同一个容器中有多个过程,系统将很难排错。因为来自不同过程的日志将被混合在一起,很难管理进程的生命周期,例如当父进程死亡后处理“僵尸”的过程。其次,一个应用程序使用多个容器将使系统更简单、更透明,并能使软件依赖关系解耦。同时,更精细颗粒的容器可以在团队之间重复使用。
问题三:Kubernetes如何在一个POD里放置多个容器?
假设有两个容器,一个是 nginx 容器,做静态服务器,一个是 git-sync 容器,用于定时监测 git 仓库上代码的变化,拉取最新代码到本地。这是两个独立的容器,如果把它们放在一个 Pod 里面,Pod 的特点是内部网络共享、数据空间共享。这样就大大减少了原先跨容器访问的复杂度。这里通过配置文件来启动包含 nginx、git-sync 容器的 Pod,使用Deployment的方式创建多个Container,还有很多的其他方式,比如在POD中定义,通过ReplicationController等方式。
配置文件 nginx-git.yml 具体内容如下:(官方参考文档):
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-git
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx # 启动 nginx 容器
image: nginx
volumeMounts: # 挂载数据卷
- mountPath: /usr/share/nginx/html
name: git-volume
- name: git-sync # 启动 git-sync 容器
image: openweb/git-sync
env:
- name: GIT_SYNC_REPO
value: "https://github.com/jasonGeng88/test.git"
- name: GIT_SYNC_DEST
value: "/git"
- name: GIT_SYNC_BRANCH
value: "master"
- name: GIT_SYNC_REV
value: "FETCH_HEAD"
- name: GIT_SYNC_WAIT
value: "10"
volumeMounts: # 挂载数据卷
- mountPath: /git
name: git-volume
volumes: # 共享数据卷
- name: git-volume
emptyDir: {}
问题四:POD中多个容器间如何通讯?
方法一:使用共享卷方式
在Kubernetes,你可以使用一个共享的Kubernetes卷作为一种简单而有效的方式,在一个POD内容器间进行数据共享。在大多数情况下,在主机上使用与容器内所有容器共享的目录,是足够的。
Kubernetes卷数据支持容器重启,但这些卷与POD寿命一样。这意味着卷容量(所拥有的数据)和POD存在时间一样。如果那个POD被删除,即使同类更换创建,共享卷被销毁和创建。
一个使用共享卷的标准多容器POD的用例是,在一个容器写日志和其他文件到共享目录中,其他容器从这个共享目录中可以读取数据。例如,我们可以创建一个POD:
apiVersion: v1
kind: Pod
metadata:
name: mc1
spec:
volumes:
- name: html
emptyDir: {}
containers:
- name: 1st
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
- name: 2nd
image: debian
volumeMounts:
- name: html
mountPath: /html
command: ["/bin/sh", "-c"]
args:
- while true; do
date >> /html/index.html;
sleep 1;
done
在这个例子中,我们定义了一个名为HTML的卷。它的类型是emptyDir,这意味着当一个POD被分配到一个节点时,卷先被创建,并只要Pod在节点上运行时,这个卷仍存在。正如名字所说,它最初是空的。第一容器运行nginx的服务器并将共享卷挂载到目录/ usr /share/ Nginx /HTML。第二容器使用Debian的镜像,并将共享卷挂载到目录/HTML。每一秒,第二容器添加当前日期和时间到index.html文件中,它位于共享卷。当用户发出一个HTTP请求到POD,nginx的服务器读取该文件并将其传递给响应请求的用户。
你可以检查一下,POD可以通过暴露nginx端口并使用您的浏览器访问它,或通过直接检查容器中的共享目录:
$ kubectl exec mc1 -c 1st -- /bin/cat /usr/share/nginx/html/index.html
...
Fri Aug 25 18:38:00 UTC 2017
$kubectl exec mc1 -c 2nd -- /bin/cat /html/index.html
...
Fri Aug 25 18:38:00 UTC 2017
Fri Aug 25 18:38:08 UTC 2017
方法二:使用进程间IPC通讯
一个POD里的容器共享相同的IPC命名空间,这意味着他们也可以互相使用标准进程间通信,如SystemV信号系统或POSIX共享内存。
在下面的示例中,我们定义了一个带有两个容器的POD。我们使用相同的容器镜像。第一个容器,生产者,创建一个标准的Linux消息队列,写一些随机消息,然后写一个特殊的退出消息。第二个容器,消费者,打开相同的消息队列以便读取和读取消息,直到接收到退出消息。我们还将重启策略设置为“从不”,所以只有在两个容器都终止后POD停止。
apiVersion: v1
kind: Pod
metadata:
name: mc2
spec:
containers:
- name: producer
image: allingeek/ch6_ipc
command: ["./ipc", "-producer"]
- name: consumer
image: allingeek/ch6_ipc
command: ["./ipc", "-consumer"]
restartPolicy:Never
为了验证这一点,使用kubectl 创建POD,并观察POD状态:
$kubectl get pods --show-all -w
NAME READY STATUS RESTARTS AGE
mc2 0/2 Pending 0 0s
mc2 0/2 ContainerCreating 0 0s
mc2 0/2 Completed 0 29s
现在您可以检查每个容器的日志,并验证第二个容器接收了来自第一个容器的所有消息,包括退出消息:
$ kubectl logs mc2 -c producer
...
Produced: f4
Produced: 1d
Produced: 9e
Produced: 27
$ kubectl logs mc2 -c producer
...
Produced: f4
Produced: 1d
Produced: 9e
Produced: 27
Consumed: done问题五:POD如何共享Volume
Kubernetes的Volume概念,用途和目的与Docker的Volume比较类似,但两者不能等价。首先,Kubernetes中的Valume定义在Pod上,然后被一个POD的多个容器挂载到具体的目录下。其次,Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器重启时,Volume的数据不会丢失。最后,Kubernetes支持多种类型的Volume,包括NFS,GlusterFS,云存储等先进的分布式文件系统。
Kubernetes中除了这种挂在POD上的共享存储外,还可以使用PersistentVolume和PersistentVolumeClaim这种方式来实现网络存储,不同是挂在Pod上的共享存储属于一种“计算资源”,而网络存储属于一种“实体资源”,与Pod无关。apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
containers:
- name: tomcat
image: tomcat
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
- name: loganalysis
image: loganalysis
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
volumes:
- name: app-logs
emptyDir: {}上面的配置文件中,volumes配置声明是用于定义volume的,volumeMounts是用于在Pod中引用定义的Volume。mountPath定义容器的挂在目录。volumes的配置声明有以下三种方式:
volumes:
- name: app-logs
emptyDir: {}
volumes:
- name: app-logs
hostPth:
path: "/data"
volumes:
- name: app-logs
gcePersistenDisk:
pdName: my-data-disk //my-data-disk需要先创建好 fsType: ext4emptyDir是Pod分配到Node后创建的,他的初始内容为空,pod在Node上移除以后也就被销毁了。
hostPath是挂载到宿主机上的目录,比较适用于需要永久保存的数据
gcePersistenDisk 表示使用谷歌公有云提供的磁盘
创建my-data-disk: gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk问题六:Pod的生命周期和重启策略
pod一共有四种状态,Pending,Running,Failed,Unknown。如下表:
状态值描述PendingAPIserver已经创建该server,但pod内有一个或多个容器的镜像还未创建,可能在下载中RunningPod内所有的容器已创建,且至少有一个容器处于运行状态,正在启动或重启状态FailedPod内所有容器都已退出,其中至少有一个容器退出失败Unknown由于某种原因无法获取Pod的状态比如网络不通
重启策略如下表:
重启策略描述Always容器失效时,即重启OnFailure容器终止运行,且退出码不为0 时重启Never不重启
问题七:Pod是如何调度
Pod调度依据不同的应用场景选择不同的调度方式,可以通过RC,Deployment,DaemonSet,Job,StatefulSet等方式。
方式一:RC,Deployment全自动调度
RC,Deployment的主要功能之一就是自动部署一个容器应用的多份副本,以及持续监控,保持集群内有一定数量的副本数量(配置文件指定了副本数量),如下实例:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
方式二:NodeSelect定向调度
kubernetes中的Schduler 负责实现pode的调度,他会根据一些复杂的算法,把pod调度到某一个Node上,如果你想指定某个Pod需要指定在某个Node上则可以通过NodeSelector定向调度。方式如下:第一步,给Node打上label,如下:
格式: kubectl label nodes <node-name> <label-key>=<label-value>
kubectl label nodes node1 zone=north
第二步,将pod调度到某个node上
apiVersion: v1
kind: Pod
metadata:
name: pod-with-healthcheck
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
nodeSelector: zone: north
方式三:DaemonSet特定场景调度
DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
使用 DaemonSet 的一些典型用法:
运行集群存储 daemon,例如在每个 Node 上运行 glusterd、ceph。
在每个 Node 上运行日志收集 daemon,例如fluentd、logstash。
在每个 Node 上运行监控 daemon,例如 Prometheus Node Exporter、collectd、Datadog 代理、New Relic 代理,或 Ganglia gmond。一个简单的用法是,在所有的 Node 上都存在一个 DaemonSet,将被作为每种类型的 daemon 使用。 一个稍微复杂的用法可能是,对单独的每种类型的 daemon 使用多个 DaemonSet,但具有不同的标志,和/或对不同硬件类型具有不同的内存、CPU要求。
示例:配置使得在每个节点上都有一个fluentd 容器
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: fluentd-cloud-logging
namespace: kube-system
labels:
k8s-app: fluentd-cloud-logging
spec:
template:
metadata:
namespace: kube-system
labels:
k8s-app: fluentd-cloud-logging
spec:
containers:
- name: fluentd-cloud-logging
images: gcr.io/google_containers/fluentd-elasticsearch:1.17
resources:
limits:
cpu: 100m
memory: 200Mi
env:
- name: FLUENTD_ARGS
value: -q
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: false
- name: containers
mountPath: /var/lib/docker/containers
volumes:
- name: containers
hostPath:
path: /var/lib/docker/containers
- name: varlog
hostPath:
path: /var/log
方式四:StatefuleSet带有状态的Pod部署
StatefulSet是一种给Pod提供唯一标志的控制器,它可以保证部署和扩展的顺序。
Pod一致性:包含次序(启动、停止次序)、网络一致性。此一致性与Pod相关,与被调度到哪个node节点无关。
稳定的次序:对于N个副本的StatefulSet,每个Pod都在[0,N)的范围内分配一个数字序号,且是唯一的。
稳定的网络:Pod的hostname模式为名称(序号)。
稳定的存储:通过VolumeClaimTemplate为每个Pod创建一个PV。删除、减少副本,不会删除相关的卷。
参考下面的实例:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: disk-ssd
mountPath: /data
volumeClaimTemplates:
- metadata:
name: disk-ssd
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "alicloud-disk-ssd"
resources:
requests:
storage: 20Gi
验证服务伸缩性
创建Statefulset服务:
# kubectl create -f statefulset.yaml
# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 21m
web-1 1/1 Running 0 20m
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
disk-ssd-web-0 Bound d-2ze9k2rrtcy92e97d3ie 20Gi RWO alicloud-disk-ssd 21m
disk-ssd-web-1 Bound d-2ze5dwq6gyjnvdcrmtwg 20Gi RWO alicloud-disk-ssd 21m
扩容服务到3个Pod,显示会创建新的云盘卷:
# kubectl scale sts web --replicas=3
statefulset.apps "web" scaled
# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 24m
web-1 1/1 Running 0 23m
web-2 1/1 Running 0 2m
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
disk-ssd-web-0 Bound d-2ze9k2rrtcy92e97d3ie 20Gi RWO alicloud-disk-ssd 24m
disk-ssd-web-1 Bound d-2ze5dwq6gyjnvdcrmtwg 20Gi RWO alicloud-disk-ssd 24m
disk-ssd-web-2 Bound d-2zea5iul9f4vgt82hxjj 20Gi RWO alicloud-disk-ssd 2m
缩容服务到2个Pod,显示pvc/pv并不会一同删除:
# kubectl scale sts web --replicas=2
statefulset.apps "web" scaled
# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 25m
web-1 1/1 Running 0 25m
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
disk-ssd-web-0 Bound d-2ze9k2rrtcy92e97d3ie 20Gi RWO alicloud-disk-ssd 25m
disk-ssd-web-1 Bound d-2ze5dwq6gyjnvdcrmtwg 20Gi RWO alicloud-disk-ssd 25m
disk-ssd-web-2 Bound d-2zea5iul9f4vgt82hxjj 20Gi RWO alicloud-disk-ssd 3m
再次扩容到3个Pod,新的pod会复用原来的PVC/PV:
# kubectl scale sts web --replicas=3
statefulset.apps "web" scaled
# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 27m
web-1 1/1 Running 0 27m
web-2 1/1 Running 0 2m
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
disk-ssd-web-0 Bound d-2ze9k2rrtcy92e97d3ie 20Gi RWO alicloud-disk-ssd 27m
disk-ssd-web-1 Bound d-2ze5dwq6gyjnvdcrmtwg 20Gi RWO alicloud-disk-ssd 27m
disk-ssd-web-2 Bound d-2zea5iul9f4vgt82hxjj 20Gi RWO alicloud-disk-ssd 5m
删除StatefulSet服务,PVC、PV并不会随着删除;
验证服务稳定性
删除一个Pod前,Pod引用PVC:disk-ssd-web-1
# kubectl describe pod web-1 | grep ClaimName
ClaimName: disk-ssd-web-1
# kubectl delete pod web-1
pod "web-1" deleted
删除Pod后,重新创建的Pod名字与删除的一致,且使用同一个PVC:
# kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 29m
web-1 1/1 Running 0 6s
web-2 1/1 Running 0 4m
# kubectl describe pod web-1 | grep ClaimName
ClaimName: disk-ssd-web-1
验证服务高可用性
云盘中创建临时文件:
# kubectl exec web-1 ls /data
lost+found
# kubectl exec web-1 touch /data/statefulset
# kubectl exec web-1 ls /data
lost+found
statefulset
删除Pod,验证数据持久性:
# kubectl delete pod web-1
pod "web-1" deleted
# kubectl exec web-1 ls /data
lost+foundstatefulset
更多请参考:
https://yq.aliyun.com/articles/629007
https://jimmysong.io/kubernetes-handbook/concepts/statefulset.html