控制器

pod作为容器的载体,需要通过deployment、daemonset、rc、job等对象来完成一组Pod的调度与自动控制功能。
deployment/RC:持续监控和维护pod副本的数量,始终将其维护在用户指定的数量。

Deployment 为例,我和你简单描述一下它对控制器模型的实现:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
上面的yaml确保携带了 app=nginx 标签的 Pod 的个数,永远等于 spec.replicas 指定的个数,即 2 个。
实现该功能的组件是:kube-controller-manager

控制器原理:

for {
  实际状态 := 获取集群中对象X的实际状态(Actual State)
  期望状态 := 获取集群中对象X的期望状态(Desired State)
  if 实际状态 == 期望状态{
    什么都不做
  } else {
    执行编排动作,将实际状态调整为期望状态
  }
}
实际状态:来自于 Kubernetes 集群本身。比如,kubelet 通过心跳汇报的容器状态和节点状态,或者监控系统中保存的应用监控数据,或者控制器主动收集的它自己感兴趣的信息

期望状态:一般来自于用户提交的 YAML 文件。比如,Deployment 对象中 Replicas 字段的值,保存在 Etcd 

图:deployment replicaSet pod之间的关系

pod中服务的升级方式

不建议的方式:停止与该服务相关的Pod,然后下载新版本镜像并创建新的Pod。
滚动升级功能:如果Pod是通过Deployment创建的,用户可以在运行时修改deployment的Pod定义,自动即可完成Deployment的自动更新操作。如果更新发生了错误,则可以通回滚操作回复Pod的版本。
1、Deployment的升级
如原本Deployment的定义如下:
nginx-deployment.yaml
apiVersion: apps/v1
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
kubectl create --filename=nginx-deployment.yaml --record=true
说明:--record=true会记录每次pod变更的记录

已运行的Pod副本数量有三个:
kubectl get pods

现在要将上面的nginx升级
方式一:通过 kubectl set image来设置
kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
方式二:通过kubectl edit deployment/nginx-deployment
将spec.template.spec.containers[0].iamge从Nginx:1.7.9更改为 nginx:1.9.1
一旦镜像名发生了修改,则将触发系统完成Deployment所有运行Pod的滚动升级操作,可以使用kubectl rollout status命令查看Deployment的更新过程。

deployment完成Pod更新的过程:
指令kubtctl describe deployment/nginx-deployment可以查看,具体过程如下:
1)初始创建Deployment时,系统会创建一个ReplicatSet,并按照用户需求创建3个pod副本
2)当更新Deployment时,系统会创建一个新的ReplicaSet,然后其副本数开始按照新定义文件创建一个Pod,此时老的ReplicaSet会缩减为2
3)最终新的ReplicatSet拥有三个Pod副本,老的ReplicatSet拥有0个副本

中间过程图示:


可以通过如下指令获取ReplicatSet的状态:
kubectl get rs

在升级的过程中,会确保可用的Pod在一定范围之内,会将可用的Pod数维持在指定副本的75%-125%之间。
在Deployment的定义中spec.strategy.type可以指定Pod的更新策略,有两种:
Recreate:重建,在更新POd时,会先杀掉所有的Pod,然后创建新的Pod
RollingUpdate:滚动更新,默认的更新方式。同时有两个参数:
1)spec.strategy.rollingUpdate.maxUnvaliable:不可用Pod状态的上线,默认25%
2)spec.strategy.rollingUpdate.maxSurge:超过Pod副本期望值数量的最大值,默认25%

deployment的回滚
假设Pod中nginx的版本设置错误,无法获取对应镜像,那么就需要回滚。
kubectl get pods #H会看到一个Pod的状态是ImagePullBackOff
kubectl  rollout history deployment/nginx-deployment #结合创建时使用--record=true,可以获取历史
kubectl  rollout history deployment/nginx-deployment  --revision=3 获取特定版本数据
kubectl  rollout undo deployment/nginx-deployment --to-revision=2 #将版本恢复到版本2

暂停和恢复Deployment的部署操作,完成个复杂的修改
kubectl rollout pause deployment/nginx-deployment #暂定更新
现在可以修改deployment的镜像信息,通过指令或者修改配置两种方式,修改完成后执行如下指令启动更新:
kubectl rollout resume deploy nginx-delpoyment
kubectl get rs #c查看当前的rs

使用kubectl rolling-update完成rc的滚动升级
DeamonSet和StatefulSet也可以更新。

升级相关命令汇总:
$ kubectl create -f nginx-deployment.yaml --record
–record 参数。它的作用,是记录下你每次操作所执行的命令,以方便后面查看。

$ kubectl rollout status deployment/nginx-deployment
实时查看 Deployment 对象的状态变化

$ kubectl get rs
Deployment 所控制的 ReplicaSet

$ kubectl edit deployment/nginx-deployment
编辑etcd里面的API对象

$ kubectl rollout status deployment/nginx-deployment
查看 nginx-deployment 的状态变化

$ kubectl describe deployment nginx-deployment
查看 Deployment 的 Events,看到这个“滚动更新”的流程
$ kubectl rollout undo deployment/nginx-deployment
把整个 Deployment 回滚到上一个版本

kubectl rollout history deployment/nginx-deployment
查看每次 Deployment 变更对应的版本。由于我们在创建这个 Deployment 的时候,指定了–record 参数,所以我们创建这些版本时执行的 kubectl 命令,都会被记录下来。

$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
deployment.extensions/nginx-deployment
回滚到的指定版本的版本号,就可以回滚到指定版本了

$ kubectl rollout pause deployment/nginx-deployment
此时 Deployment 正处于“暂停”状态,所以我们对 Deployment 的所有修改,都不会触发新的“滚动更新”,也不会创建新的 ReplicaSet。

$ kubectl rollout resume deployment/nginx-deployment
恢复Deployment的滚动更新

无状态与有状态的应用:

无状态应用:
应用的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。

有状态的应用:分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。
还有就是数据存储类应用,它的多个实例,往往都会在本地磁盘上保存一份数据。而这些实例一旦被杀掉,即便重建出来,实例与数据之间的对应关系也已经丢失,从而导致应用失败。


k8s有了新的控制器:StatefulSet
应用状态被抽象为两类:
1拓扑状态
应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,删除重建也要是相同的顺序。
并且,新创建出来的Pod,必须和原来Pod的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。

2 存储状态
应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。

StatefulSet的核心功能,就是通过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新Pod 恢复这些状态。


HealdessService
Service 被访问的两种方式:
1vip:service的虚拟ip,会把请求转发到该 Service 所代理的某一个 Pod 上。
2dns:Service 的 DNS 方式。比如访问“my-svc.my-namespace.svc.cluster.local”这条 DNS 记录,就可以访问到名叫 my-svc 的 Service 所代理的某一个 Pod。

dns又可以分为两种方式:
Normal Service:访问“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP
Headless Service:访问“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一个 Pod 的 IP 地址。
区别在于:Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。

标准的 Headless Service 对应的 YAML 文件:
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None #重点关注该字段
  selector:
    app: nginx
clusterIP 字段的值是:None,这个service不会有vip。且只能以dns的方式暴露代理的Pod。
注意: Headless Service所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,即k8s为Pod 分配的唯一的“可解析身份”,如下所示:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local

所以:知道了一个 Pod 的名字,以及它对应的 Service 的名字,就可以通过这条DNS记录访问到Pod的IP地址。

那么,StatefulSet 又是如何使用这个 DNS 记录来维持 Pod 的拓扑状态的呢?
编写一个 StatefulSet 的 YAML 文件,如下所示:
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx" #重点关注,对应的headless service
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80
          name: web

创建上面的service和statefulSet:
$ kubectl create -f svc.yaml
$ kubectl get service nginx
NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     ClusterIP    None         <none>        80/TCP    10s

$ kubectl create -f statefulset.yaml
$ kubectl get statefulset web
NAME      DESIRED   CURRENT   AGE
web       2         1         19s


$ kubectl get pods -w -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
......
web-0     1/1       Running   0         19s
web-1     0/1       Pending   0         0s
......
web-1     1/1       Running   0         20s

StatefulSet 给它所管理的所有 Pod 的名字,进行了编号,编号规则是:<statefulset name>-<ordinal index>。

kubectl exec 命令进入到容器中查看它们的 hostname:

$ kubectl exec web-0 -- sh -c 'hostname'
web-0
$ kubectl exec web-1 -- sh -c 'hostname'
web-1

Pod 的 hostname 与 Pod 名字是一致的,都被分配了对应的编号。

启动了一个一次性的 Pod,因为 --rm 意味着 Pod 退出后就会被删除掉。然后,在这个 Pod 的容器里面,我们尝试用 nslookup 命令,解析一下 Pod 对应的 Headless Service:
$ kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
$ nslookup web-0.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.7

$ nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.7

当我们把这两个 Pod 删除之后,Kubernetes 会按照原先编号的顺序,创建出了两个新的 Pod。并且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:web-0.nginx 和 web-1.nginx。

Kubernetes 就成功地将 Pod 的拓扑状态(比如:哪个节点先启动,哪个节点后启动),按照 Pod 的“名字 + 编号”的方式固定了下来。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录。

尽管 web-0.nginx 这条记录本身不会变,但它解析到的 Pod 的 IP 地址,并不是固定的。这就意味着,对于“有状态应用”实例的访问,你必须使用 DNS 记录或者 hostname 的方式,而绝不应该直接访问这些 Pod 的 IP 地址。

核心:
headlessService 通过dns解析获取对应的pod的ip
stateful通过对pod的名称增加编号方式,保证pod的拓扑性。

Headless Service中不通过vip,外部访问的时候,只能访问到固定的一个pod。 那statefulSet中的replicas定义只能用作主备?

StatefulSet 之存储状态
pv:持久化存储
pvc:持久化存储声明
pvc主要是降低了用户声明和使用持久化Volume的门槛。


使用volume的过程:
一:定义一个 PVC,声明想要的 Volume 的属性:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: pv-claim
spec:
  accessModes:
  - ReadWriteOnce :可读写,且只能被挂载到一个节点,不可共享
  resources:
    requests:
      storage: 1Gi #需要的volume大小为1G

二:应用的Pod中,声明使用该PVC
apiVersion: v1
kind: Pod
metadata:
  name: pv-pod
spec:
  containers:
    - name: pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: pv-storage
  volumes:
    - name: pv-storage
      persistentVolumeClaim:
        claimName: pv-claim #pvc名称

运维定义volume:
kind: PersistentVolume
apiVersion: v1
metadata:
  name: pv-volume
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  rbd: # Ceph RBD Volume 的详细定义
    monitors:
    # 使用 kubectl get pods -n rook-ceph 查看 rook-ceph-mon- 开头的 POD IP 即可得下面的列表
    - '10.16.154.78:6789'
    - '10.16.154.82:6789'
    - '10.16.154.83:6789'
    pool: kube
    image: foo
    fsType: ext4
    readOnly: true
    user: admin
    keyring: /etc/ceph/keyring

在statefulSet中增加pvc定义:
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.9.1
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
额外添加了一个 volumeClaimTemplates 字段。从名字就可以看出来,它跟 Deployment 里 Pod 模板(PodTemplate)的作用类似。也就是说,凡是被这个 StatefulSet 管理的 Pod,都会声明一个对应的 PVC;更重要的是,这个 PVC 的名字,会被分配一个与这个 Pod 完全一致的编号。
如下:
$ kubectl create -f statefulset.yaml
$ kubectl get pvc -l app=nginx
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           48s
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           48s

PVC,都以“<PVC 名字 >-<StatefulSet 名字 >-< 编号 >”的方式命名,并且处于 Bound 状态。

向volume中写入内容:
$ for i in 0 1; do kubectl exec web-$i -- sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done

获取写入的内容
$ for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
hello web-0
hello web-1

kubectl delete 命令删除这两个 Pod:
$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted


# 在被重新创建出来的Pod容器里访问http://localhost
$ kubectl exec -it web-0 -- curl localhost
hello web-0

一个 Pod,比如 web-0,删除之后,这个 Pod 对应的 PVC 和 PV,并不会被删除,而这个 Volume 里已经写入的数据,也依然会保存在远程存储服务里。

StatefulSet 控制器发现,一个名叫 web-0 的 Pod 消失了。所以,控制器就会重新创建一个新的、名字还是叫作 web-0 的 Pod 来
在这个新的 Pod 对象的定义里,它声明使用的 PVC 的名字,还是叫作:www-web-0。

所以,在这个新的 web-0 Pod 被创建出来之后,Kubernetes 为它查找名叫 www-web-0 的 PVC 时,就会直接找到旧 Pod 遗留下来的同名的 PVC,进而找到跟这个 PVC 绑定在一起的 PV。

总结:StatefulSet 其实就是一种特殊的 Deployment,而其独特之处在于,它的每个 Pod 都被编号了,体现在 Pod 的名字和 hostname 等标识信息上,这不仅代表了 Pod 的创建顺序,也是 Pod 的重要网络标识(即:在整个集群里唯一的、可被访问的身份)。

StatefulSet 就使用 Kubernetes 里的两个标准功能:Headless Service 和 PV/PVC,实现了对 Pod 的拓扑状态和存储状态的维护。


Daemonset:每一个节点上有一个这样的pod实例,新节点加入,pod会自动创建。
应用场景包括:网络插件 存储插件 监控和日志组件
实现原理:etcd获取node列表+控制器模型

job与cronJob
处理离线业务,在计算完成后就直接退出了;
 

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐