kubernetes in action读书笔记(四)ConfigMap、Secret、滚动升级、downwardAPI、Deployment、Statefulset

  • ConfigMap
  • Secret
  • 如何访问pod的元数据
  • 由滚动升级引出Deployment
  • 原始的RC人工滚动升级
  • 缺陷
  • Deployment
  • Statefulset
  • 读书前对它们的理解


ConfigMap

没有太多好说的,一个用来解决服务配置的资源,可以避免硬编码,相对自由的在pod中使用。我们可以将configMap作为环境变量使用,也可以将configMap作为一个配置文件使用(直接映射到持久卷上),因此在创建configMap到时候,可以通过字面量创建,也可以将一个文件或者目录创建为一个configMap,需要注意的是,在使用configMap挂载到一个持久卷时,会将对应的目录其他文件覆盖掉,这个需要关注,避免把有用的文件覆盖掉了。

Secret

和configMap差不多都是作为pod的配置属性来使用的,不过secret不在磁盘存储,都是在内存中的,因此安全性上要比configMap要高。

如何访问pod的元数据

应用程序往往需要访问当前pod的一些基本的在K8S集群中的信息,比如版本号、镜像名称、hostname、ip等等,可以一般可以通过以下几种方式来获取:

通过环境变量获取元数据
看一段代码

env:
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name

这样能拿到pod的元数据,但是缺点是一旦这些元数据更新,这种环境变量的方式是不会更新的,必须重启pod。

通过downward API获取元数据
看一段代码

metadata:
  name: downward
  labels: 
    foo: bar
  annotations:
    key1: value1
    key2:
      multi
      line
      value
spec:
  containers:
    volumeMounts:
    - name: downward
      mountPath: /etc/downward
volumes:
- name: downward
  downwardAPI:
    items:
    # podName会被写入到/etc/downward/podName这个文件里
    - path: "podName"
      filedRef:
        fieldPath: metadata.name
    # labels会被写入到/etc/downward/labels这个文件里
    - path: "labels"
      filedRef:
        fieldPath: metadata.labels

所以通过这种方式,只要在volumes下定义的内容,最后都会在volumeMounts下对应到一个文件,而且此时如果当元数据发生变化是,文件也会对应的刷新,环境变量就不不会刷新了,除了这个优点之外,环境变量也只能在当前pod中使用,如果一个pod有多个容器,那就得分开定义不能用重名的环境变量,但是通过downwardAPI就可以使用同样的名字,但是放在不同的目录下就行了。如果是只需要获取一次pod元数据的情况下,通过env其实也行。
downwardAPI解决了数据刷新以及多个容器在一个pod的问题,但是能够获取的数据是有限的(metadata.XXXX),如果需要更多的信息,需要通过k8s的api server查询。

通过api server获取元数据
不进行赘述,这个属于在服务内部实施的,即时性和数据的范围都是最大的。不过有个点倒是值得注意,书中提到了一种ambassador容器,类似一个代理容器,用来解决所有和api server的交互问题。因为和api server交互是需要鉴权认证的,不希望每个服务都做重复的事情,因此需要这个容器来解决公共的需求。

由滚动升级引出Deployment

原始的RC人工滚动升级

从第一张到现在基本上能够看到的资源就是RC(ReplicaController)和RS(ReplicaSet),我们平时最常用的Deployment和Statefulset始终没有出现过,本章总算讲到了Deployment。
RC本身已经是一种对pod声明式的管理,我们在RC中的定义包括replicas、images、selectors等等,K8S在进行根据定义对pod进行操作时,都是通过对定义的内容进行声明式的扫描和管理,需要什么样的资源,就提供什么样的资源,而不是RC本身定义好了之后,去向K8S去申请。Deployment被引出的一个关键点在于应用的升级,一个RC的典型升级过程如下:

1、当前RC有3个pod,都是v1版本,要升级到v2版本
2、修改RC的版本定义,改成V2
3、此时K8S 发现RC需要的pod版本是v2,就创建v2版本的pod
4、与此同时,v1版本的pod并不会自己销毁,所以需要手动销毁
5、销毁过程伴随服务不可用
6、等待v2版本pod都起来之后,服务恢复正常

为了避免出现服务不可用情况,考虑等v2版本的pod起来了,再删掉v1版本的pod,将上面从第三步开始,稍作修改:

等待v2版本的pod都完成创建,同时,v2版本的pod将标签修改的和v1不同
将SVC的labelSelector修改到v2版本的标签,这样SVC就将流量切换到了v2版本的pod上
删除v1版本的pod

通过这种方式,服务升级过程是没有中断的。只不过需要手动改一下RC的标签,手动修改一下SVC的labelSelector,最后再恢复回来。设想一下,如果我们直接使用两个RC来操作整个过程,是不是更简单一点?

1、创建v2版本的RC,v2版本的RC对应v2版本的pod创建,v1/v2 pod的labelSelector是一样的,只是RC名字不一样,所以v1/v2的pod创建好之后都会挂载一个SVC上
2、V2的RC创建好一个pod,V1就将replica减少1个
3、直到V2的RC全部建好,V1 RC的replica减少到0
需要注意的是第一步,v2版本的RC创建出来的pod,可能会被v1版本的RC识别并删除掉,所以在升级过程中两个版本的RC的labelSelector是不同的
假设V1版本的是:Selector:app=myService
为了让SVC能够识别到v2版本的pod,所以v2版本的pod也得具备app=myService这个标签,为了避免冲突,所以在v1/v2的RC分别都增加了一个标签:
v1的RC:Selector:app=myService,version=v1
v2的RC:Selector:app=myService,version=v2
这样一来,两个RC就是各论各的pod,而SVC的labelSelector只包含了app=myService,所以v1/v2的pod都会挂在SVC下

这个过程相比之前,简单的地方就在于我们不用在操作pod了,只用控制量两个RC的状态就行,在不同的时期对两个RC的replica个数进行控制,而且也不用改SVC了。

缺陷

这种人工手动升级的方式,首先得自己写代码控制状态,比较费劲,其次最大的问题在于这个升级状态是客户端执行的,是通过一个客户端程序对RC进行修改来实现的,如果说次数客户端的机器发生故障,那么升级也就中断了,可能会造成很大的损失,而且kubernetes一直强调的都是通过声明的方式来实现对系统状态的改变,所以应该考虑,如何在不费劲不手工的情况下,由集群本身对声明进行管理来实现滚动升级。

Deployment

按刚才说的,其实创建了一个deployment的同时其实也创建了一个ReplicaSet(新一代的ReplicaController),我们在使用deployment的过程,对pod管理其实是由RS来干的。yaml就不进行列举了,对比ReplicaController创建出的pod,样子看起来就不太一样~
RC声明的pod:

NAME					READY		STATUS
myService-musda			1/1			running
myService-bfzxd			1/1			running

Deployment声明的pod:

NAME							READY		STATUS
myService-1918372-sdgfs			1/1			running
myService-5981813-qasda			1/1			running

中间多了个数字,这个数字是pod模板的hash值,这样一方面deployment可以创建多个RS,另一方面这个名字既然和pod的模板hash一致,那也就能减少了不必要的名称的浪费,这些对应关系啥都要在etcd管理,这种方式也能减少不必要的名称膨胀。列举这个名字的区别主要是希望能够理解到,deployment内部本质上还是一个/多个的RS,刚才费半天劲说的滚动升级过程,在deployment中就是一个配置的操作就能搞定。而且deployment还顺便把回滚做了,每次滚动升级后,原先的RS并不会被立刻删除,deployment会保留一段时间内的RS,当发生回滚操作时,立刻就可以启用之前版本的RS资源,历史版本的默认数量是2。
感觉真正讲到了deployment又没有太多的内容,基本上还是沿用了RS/RC的思想,不过支持了滚动升级和回滚,其他的一些特性/场景,后面看看是不是讲到了kubernetes原理后有更多的细节介绍。

Statefulset

之前对statefulset的理解就是当需要挂盘的时候,才用到了statefulset,原因是statefulset的名称是固定的,一般都是my-service-0这种格式,如果是多实例,那就会逐个递增,当发生replica个数改变或重启时,pod的名称不变还是这个,所以对应的pvc啥的都不变,因为挂盘信息呀啥的都是基于名称在etcd中记录的映射关系,这种名字不变的pod就能实现有状态了。
除了上面说的这个名字会变的原因之外,另一个原因就是deployment或者说ReplicaSet是无法为多个pod创建多个PVC的,所以如果非要给ReplicaSet下的pod每个都搞一个独立的存储,那就得:

1、手动一个个创建PVC并且挂盘,这种方式就是不能重启,重启了就得再来一遍
2、一个pod一个ReplicaSet,这样每个都各申请各的,这种办法也比较挫;
3、ReplicaSet的多个pod用一个卷的多个不同的目录,这个得像个办法在pod创建之后通过一个第三方的服务来为多个pod进行目录使用权的分配。

mark一下,所以说ReplicaSet不能给每个pod创建pvc?why?是因为RS下的pod名字老变所以只能按照RS的名称来创建PVC的对应关系吗?前面看过,忘了~

除了上面说到的statefulset可以为pod提供一对一的稳定的pvc,同时statefulset所管理的pod还具备稳定的ip地址,所以对于希望服务端ip地址固定的应用场景,statefulset就能发挥作用,有一种山寨的办法是利用SVC,为每一个deployment都创建一个SVC,这种办法倒是能让pod的ip固定。

之前在使用statefulset时要创建pvc,当时遇到的问题记录了一下,文章内容搬过来,也是关于deployment和statefulset的。

读书前对它们的理解

=====================================================================
今天在处理一个PVC无法自动创建的问题时,顺便理了一下。

在创建一个Deployment时发现PVC无法自动创建,查了一下相关内容,主要参考一下文章:
https://akomljen.com/kubernetes-persistent-volumes-with-deployment-and-statefulset/ 其中:
Kubernetes in Action中文版pdf下载 kubernetes in action pdf 英文版_Deployment
译:可以通过ReadWriteOnce模式来给Deployment创建一个PVC,这样可以正常运行,但是前提是不能扩展Deployment,因为如果一旦有多个pod,那么当一个节点上的pod在使用pvc时,另一个pod在另一个节点上就没法用了,而且即便两个pod在一个节点上,都用一个pvc也会报错;(ReadWriteOnce意思是该pvc一次只能有一个node访问)
Kubernetes in Action中文版pdf下载 kubernetes in action pdf 英文版_元数据_02
译:如果想跑一个有状态的服务,那就用stateful set,这样有很多好处,特别是你不用自己在创建PVC了,而且扩展起来特别容易,通过stateful set,可你可以定义volumeClaimTemplates来让每个pod自己自动创建pvc,你只用在一个地方定义你的持久卷(statefulset的yaml)

目前1.5版本以下还不支持Deployment用这种方式自动创建PVC/PV

Kubernetes in Action中文版pdf下载 kubernetes in action pdf 英文版_环境变量_03


顺便翻查了一下stateful set和deployment的区别,几篇blog写的可以参考。

https://draveness.me/kubernetes-statefulset statefulset实现原理

http://www.unmin.club/?p=696

headeless.service的保证原理(通过这个文章发现我目前使用的stateful set的方式是不合理的,完全是为了用持久卷而用stateful set,因为clusterIp不是none,请求并不会稳定的转发到一个pod上,所以说白了不同实例建其实还是无状态的)

https://www.jianshu.com/p/18042b5f2682

stateful set deployment整体对比

另外mark一条老是记不住也查半天的命令:iptables -S -t nat

2020-01-15补充
kubernetes在应用过程最终都是围绕pod进行的管理,deployment/statefulset都作用于pod,作为不同的controller,两者的主要区别是deployment在调用api-server/scheduler时,不会指定固定名称的pod;statefulset在每次申请pod的时候,都会固定pod的名字,类似myservice-0、myservice-1,这个绑定关系存在,所以可以把这个关系再搭上一个PVC,就能实现statefulset的有状态了。

顺便补充一下关于调度的理解,整体看提交的过程,大体上是:

  1. 客户端提交一次请求到api-server创建一个deployment,持久化到etcd
  2. deployment controller订阅api-server发现有deployment要部署,获取到之后根据deployment的描述调用api-server创建pod,持久化到etcd
  3. scheduler订阅api-server,从etcd中发现需要创建pod,则根据pod属性找到对应的node
  4. node订阅scheduler发现有一个pod属于自己,则下载对应的image并拉起对应的pod