文章目录

  • 一、资源对象管理
    • 1、资源对象概述
      • (1)Pod
      • (2)Replication Controller & RC (副本控制器)
      • (3)Replica Set & RS (副本集)
      • (4)Deployment (部署)
      • (5)Service (服务)
      • (6)Job (任务)
      • (7)DaemonSet (后台支撑服务集)
      • (8)StatefulSet (有状态服务集)
      • (9)Volume (存储卷)
      • (10) PVC && PV
      • (11)Secret
      • (12)用户帐户(User Account)和服务帐户(Service Account)
      • (13)Namespace (命令空间)
      • (14)RBAC访问授权
      • (15)Node
      • (16)集群联邦(Federation)
    • 2、k8s的对象
      • (1)对象
      • (2)对象 Spec 与状态
      • (3)对象描述
    • 3、pod
      • (1)基础概述
      • (2)init容器
        • a、功能
        • b、功能举例:
        • c、例子
      • (3)Pause容器
      • (4)容器生命周期
        • a、容器探针
        • b、livenessProbe和readinessProbe使用场景
        • c、Pod重启策略及调度
        • d、配置项:参考相关接口文档
      • (5)pod hook测试
        • 说明:
      • 官方yaml:
        • 修改yaml文件
        • 操作步骤:
        • 遗留问题:
      • (6)Pod Preset
      • (7)Disruptions(中断)及PodDisruptionBudget(PDB,中断预算)
        • 自愿中断和非自愿中断
        • 减轻非自愿性中断的方法:
        • pdb
        • 相关连接:
      • (8)一个测试的例子
    • 4、零碎的东西
      • (1)标签:
      • (2)annotation(注解)
      • (3)Taint和Toleration(污点和容忍)
      • (4)亲和性、反亲和性
      • (5)Horizontal Pod Autoscaling(水平自动扩展)
      • (6)Garbage Collection(垃圾收集器)
    • 5、controller(控制器)
      • (1)ReplicaSet
      • (2)Deployment
        • a、简介
        • b、创建Deployment
        • c、升级Deployment
        • d、回滚deployment
        • e、Deployment 扩容
        • f、暂停和恢复Deployment
        • g、Deployment 状态
          • 失败的Deployment:
          • 注意:
          • 失败的deployment的操作
        • h、清理Policy
        • i、编写Deployment Spec
          • Pod Template
          • Replicas
          • Selector
          • Strategy
          • Progress Deadline Seconds
          • Min Ready Seconds
          • Rollback To
          • Revision History Limit
          • pause
          • nginx-deployment中用kubectl edit获取的配置
          • kubectl patch
        • i、实例:金丝雀部署
          • 官网文档
          • 测试:
          • 生产环境金丝雀发布:
      • (3)StatefulSet
      • (4)DaemonSet
      • (5)JOB & CronJob
        • a、TTL Controller for Finished Resources
    • 6、service
      • (1)先来个实验再说
        • 创建service
        • 使用yaml创建配置文件并测试sessionAffinity
        • 压测看看:
          • docker的输出filter和format使用方式
          • 测试kube-proxy的实时内存消耗
          • 对单pod、cluster ip、nodePort进行压测
      • (2)service
      • (3)Ingress
        • a、简介
        • b、ingress controller
        • c、The Ingress Resource
        • d、Types of Ingress
          • single sevice ingress
          • simple fanout
          • Name based virtual hosting
          • TLS
        • e、Updating an Ingress
        • f、对于边缘节点也就是ingress controller一点个人看法
      • (4)Adding entries to Pod /etc/hosts with HostAliases
      • (5)Network Policy
    • 7、存储
      • (1)secret
        • a、Opaque
        • b、kubernetes.io/dockerconfigjson
        • c、Service Account
      • (2)configmap
        • a、创建configmap
          • 从目录创建
          • 从文件创建
          • 从字面量创建
        • b、pod中使用configmap
          • 代替环境变量
          • 用ConfigMap设置命令行参数
          • 通过数据卷插件使用ConfigMap
        • c、注意:
      • (3)volume
        • a、部分volume类型
          • cephfs
          • configMap
          • emptyDir
          • glusterfs
          • Downward API
          • hostPath
          • local
          • nfs
          • persistentVolumeClaim
          • projected
          • secret
        • b、使用子路径
        • c、资源
      • (4)persistent volume
        • a、PV
        • b、PV的种类
        • c、PVC
        • d、Claims As Volumes
        • e、raw block volume的支持(1.9是alpha)
        • f、卷和声明的生命周期
      • (5)storage class
        • a、简介
        • b、StorageClass资源
          • Provisioner
          • parameters
      • (6)Dynamic Volume Provisioning

一、资源对象管理

大量内容参考了https://k8smeetup.github.io
还有部分没有整理完成
基于1.11

1、资源对象概述

下面表格中的对象都可以在yaml文件中作为一种API类型来配置:

类别 名称
资源对象 Pod、ReplicaSet、ReplicationController、Deployment、StatefulSet、DaemonSet、Job、CronJob、HorizontalPodAutoscaling、Node、Namespace、Service、Ingress、Label、CustomResourceDefinition
存储对象 Volume、PersistentVolume、Secret、ConfigMap
策略对象 SecurityContext、ResourceQuota、LimitRange
身份对象 ServiceAccount、Role、ClusterRole

(1)Pod

  • Pod是k8s的最小调度单位,它由多个容器构成,容器间共享namespace、进程等;但是一般不会直接调度单独的Pod,会根据业务类型来决定
  • k8s中业务划分及赌赢的调度方法
    • 长期伺服型(long-running) —— Deployment
    • 批处理型(batch) —— Job
    • 节点后台支撑型(node-daemon) —— DaemonSet
    • 有状态应用型(stateful application) —— StatefulSet

(2)Replication Controller & RC (副本控制器)

  • RC是Kubernetes集群中最早的保证Pod高可用的API对象,只适用于长期伺服型的业务类型
  • 通过监控运行中的Pod来保证集群中运行指定数目的Pod副本。
    • 指定的数目可以是多个也可以是1个;
    • 少于指定数目,RC就会启动运行新的Pod副本;
    • 多于指定数目,RC就会杀死多余的Pod副本。
    • 即使在指定数目为1的情况下,通过RC运行Pod也比直接运行Pod更明智,因为RC也可以发挥它高可用的能力,保证永远有1个Pod在运行。

(3)Replica Set & RS (副本集)

  • RS是新一代RC,提供同样的高可用能力,能支持更多种类的匹配模式。
  • 副本集对象一般不单独使用,而是作为Deployment的理想状态参数使用。

(4)Deployment (部署)

部署表示用户对Kubernetes集群的一次更新操作。部署是一个比RS应用模式更广的API对象,可以是创建一个新的服务,更新一个新的服务,也可以是滚动升级一个服务。滚动升级一个服务,实际是创建一个新的RS,然后逐渐将新RS中副本数增加到理想状态,将旧RS中的副本数减小到0的复合操作;这样一个复合操作用一个RS是不太好描述的,所以用一个更通用的Deployment来描述。以Kubernetes的发展方向,未来对所有长期伺服型的的业务的管理,都会通过Deployment来管理。

(5)Service (服务)

RC、RS和Deployment只是保证了支撑服务的微服务Pod的数量,但是没有解决如何访问这些服务的问题。一个Pod只是一个运行服务的实例,随时可能在一个节点上停止,在另一个节点以一个新的IP启动一个新的Pod,因此不能以确定的IP和端口号提供服务。要稳定地提供服务需要服务发现和负载均衡能力。服务发现完成的工作,是针对客户端访问的服务,找到对应的的后端服务实例。在K8集群中,客户端需要访问的服务就是Service对象。每个Service会对应一个集群内部有效的虚拟IP,集群内部通过虚拟IP访问一个服务。在Kubernetes集群中微服务的负载均衡是由Kube-proxy实现的。Kube-proxy是Kubernetes集群内部的负载均衡器。它是一个分布式代理服务器,在Kubernetes的每个节点上都有一个;这一设计体现了它的伸缩性优势,需要访问服务的节点越多,提供负载均衡能力的Kube-proxy就越多,高可用节点也随之增多。与之相比,我们平时在服务器端做个反向代理做负载均衡,还要进一步解决反向代理的负载均衡和高可用问题。

(6)Job (任务)

  • 批处理业务与长期伺服业务的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。
  • Job管理的Pod根据用户的设置把任务成功完成就自动退出了。成功完成的标志根据不同的spec.completions策略而不同
    • 单Pod型任务有一个Pod成功就标志完成;
    • 定数成功型任务保证有N个任务全部成功;
    • 工作队列型任务根据应用确认的全局成功而标志成功。

(7)DaemonSet (后台支撑服务集)

典型的后台支撑型服务包括,存储,日志和监控等在每个节点上支持Kubernetes集群运行的服务。

(8)StatefulSet (有状态服务集)

1.5版本中成为beta版,1.9版本正式成为GA版

  • 无状态(stateless)、牲畜(cattle)、无名(nameless)、可丢弃(disposable)

    • 有RC、RS,控制的Pod的名字是随机设置的,名字和启动在哪儿都不重要,重要的只是Pod总数;
    • 一般不挂载存储或者挂载共享存储(保存的是所有Pod共享的状态),有问题,去其他地方再重启一个就是了
  • 有状态(stateful)、宠物(pet)、有名(having name)、不可丢弃(non-disposable)
    • StatefulSet中的每个Pod的名字都是事先确定的,不能更改,名字关联与该Pod对应的状态
    • 对于StatefulSet中的Pod,每个Pod挂载自己独立的存储,如果一个Pod出现故障,从其他节点启动一个同样名字的Pod,要挂载上原来Pod的存储继续以它的状态提供服务。
    • 比如:mysql、postgre sql、zk、etcd(zk和etcd这种分布式集群可以不用StatefulSet)

(9)Volume (存储卷)

  • Kubernetes的存储卷的生命周期和作用范围是一个Pod
  • 每个Pod中声明的存储卷由Pod中的所有容器共享。
  • 支持的类型非常多:
    • 公有云存储
    • 多种分布式存储包括GlusterFS和Ceph;
    • 也支持较容易使用的主机本地目录emptyDir, hostPath和NFS等等

(10) PVC && PV

(11)Secret

  • Secret是用来保存和传递密码、密钥、认证凭证这些敏感信息的对象。
  • 可以将敏感认证信息存入一个Secret对象,而在配置文件中通过Secret对象引用这些敏感信息
  • 好处:意图明确,避免重复,减少暴漏机会。

(12)用户帐户(User Account)和服务帐户(Service Account)

  • 用户帐户为人提供账户标识,而服务账户为计算机进程和Kubernetes集群中运行的Pod提供账户标识。

    • 用户帐户对应的是人的身份,人的身份与服务的namespace无关,所以用户账户是跨namespace的;
    • 而服务帐户对应的是一个运行中程序的身份,与特定namespace是相关的。

(13)Namespace (命令空间)

  • 命名空间为Kubernetes集群提供虚拟的隔离作用;
  • Kubernetes集群初始有两个命名空间,分别是默认命名空间default和系统命名空间kube-system,除此以外,管理员可以可以创建新的命名空间满足需要。

(14)RBAC访问授权

  • Attribute-based Access Control & ABAC(基于属性的访问控制)Kubernetes集群中的访问策略只能跟用户直接关联;
  • Role-based Access Control & RBAC(基于角色的访问控制)访问策略可以跟某个角色关联,具体的用户在跟一个或多个角色相关联。

(15)Node

minion

# 禁止pod调度到某个节点
kubectl cordon NODENAME
# 驱逐某个节点上的所有pod
kubectl drain NODENAME

(16)集群联邦(Federation)

后期再研究吧

2、k8s的对象

(1)对象

  • 状态管理:一旦创建对象,Kubernetes 系统将持续工作以确保对象存在。
  • api:对象的创建、修改、删除可以用kubectl命令行接口,或者golang客户端、python客户端参考:https://github.com/kubernetes-client/python

(2)对象 Spec 与状态

  • spec,它描述了对象的 期望状态—— 希望对象所具有的特征。
  • status 描述了对象的 实际状态,它是由 Kubernetes 系统提供和更新。在任何时刻,Kubernetes 控制平面一直处于活跃状态,管理着对象的实际状态以与我们所期望的状态相匹配。
  • 相关状态参考:https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md

(3)对象描述

  • 当创建 Kubernetes 对象时,必须提供对象的 spec,用来描述该对象的期望状态,以及关于对象的一些基本信息(例如,名称)。
  • 当使用 Kubernetes API 创建对象时(或者直接创建,或者基于kubectl),API 请求必须在请求体中包含 JSON 格式的信息。
  • 其他必须字段:
    • apiVersion - 创建该对象所使用的 Kubernetes API 的版本
    • kind - 想要创建的对象的类型
    • metadata - 帮助识别对象唯一性的数据,包括一个 name 字符串、UID 和可选的 namespace
apiVersion: apps/v1beta1
kind: Deployment
metadata:name: nginx-deployment
spec:replicas: 3template:metadata:labels:app: nginxspec:containers:- name: nginximage: nginx:latestports:- containerPort: 80
[root@server apis]# kubectl create -f nginx.yaml
deployment.apps/nginx-deployment created
[root@server apis]# kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
nginx-deployment-884c7fc54-mh2b4   1/1       Running   0          23s       172.17.1.10   client02   <none>
nginx-deployment-884c7fc54-qz6cg   1/1       Running   0          22s       172.17.1.9    client02   <none>
nginx-deployment-884c7fc54-tztmt   1/1       Running   0          22s       172.17.2.9    client01   <none>

3、pod

(1)基础概述

  • Pod 一般不会单独使用;
  • Pod操作配置yaml可以参考:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/
  • 个人理解Pod是基于一些策略将容器进行捆绑的集合,各个容器完成不同的功能(比如:基础容器、代码容器、日志收集容器[依赖架构的设计]),从而实现功能的耦合
  • 无状态的Pod最大的特性是‘用’后即焚,没有stop的概念,只有delete的概念
  • Pod删除流程:
    • 用户发送删除pod的命令,默认宽限期是30秒;
    • 在Pod超过该宽限期后API server就会更新Pod的状态为“dead”;
    • 在客户端命令行上显示的Pod状态为“terminating”;
    • 跟第三步同时,当kubelet发现pod被标记为“terminating”状态时,开始停止pod进程:
      • 如果在pod中定义了preStop hook,在停止pod前会被调用。如果在宽限期过后,preStop hook依然在运行,第二步会再增加2秒的宽限期;
      • 向Pod中的进程发送TERM信号;
    • 跟第三步同时,该Pod将从该service的端点列表中删除,不再是replication controller的一部分。关闭的慢的pod将继续处理load balancer转发的流量;
    • 过了宽限期后,将向Pod中依然运行的进程发送SIGKILL信号而杀掉进程。
    • Kublete会在API server中完成Pod的的删除,通过将优雅周期设置为0(立即删除)。Pod在API中消失,并且在客户端也不可见。
  • 强制删除注意点:
    • kubectl delete命令支持 —grace-period= 选项,1.5版本之后:必须同时使用 --force 和 --grace-period=0 来强制删除pod
    • 强制删除直接操作api server,而不等待kubelet的返回,这时Pod并没有完全删除,可能还对外提供服务,此时pod hook就非常有用了,请参考后面的pod hook的测试
  • pod特权模式:
    • 关键字:容器定义文件的 SecurityContext 下使用 privileged
    • 容器内进程可获得近乎等同于容器外进程的权限

(2)init容器

  • 参考:https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
  • Init 容器总是运行到成功完成为止。
  • Init 容器会按顺序在网络和数据卷初始化之后启动
  • 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成。如果失败,会根据restartPolicy策略决定是否重启
  • 和普通容器差不多一样的配置,不过不支持Readiness Probe
  • Pod重启,init容器会重新执行;
  • 在pod上使用activeDeadlineSeconds,在容器上使用livenessProbe,避免init容器一直失败(为init容器活跃设置了一个期限)

a、功能

  • 在业务容器启动之前,阻塞住,直到业务容器启动条件满足

b、功能举例:

  • 等待一个 Service 创建完成,通过类似如下 shell 命令:
    for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; exit 1
  • 将 Pod 注册到远程服务器,通过在命令中调用 API,类似如下:
    curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
  • 在启动应用容器之前等一段时间,使用类似 sleep 60 的命令。
  • 克隆 Git 仓库到数据卷。
  • 将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。例如,在配置文件中存放 POD_IP 值,并使用 Jinja 生成主应用配置文件。

c、例子

myapp.yaml

apiVersion: v1
kind: Pod
metadata:name: myapp-podlabels:app: myapp
spec:containers:- name: myapp-containerimage: busyboxcommand: ['sh', '-c', 'echo The app is running! && sleep 3600']initContainers:- name: init-myserviceimage: busyboxcommand: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']- name: init-mydbimage: busyboxcommand: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']

services.yaml

kind: Service
apiVersion: v1
metadata:name: myservice
spec:ports:- protocol: TCPport: 80targetPort: 9376
---
kind: Service
apiVersion: v1
metadata:name: mydb
spec:ports:- protocol: TCPport: 80targetPort: 9377
kubectl create -f myapp.yaml
kubectl get -f myapp.yaml
kubectl describe -f myapp.yamlkubectl create -f services.yaml
kubectl get -f myapp.yaml
查看相关的结果

(3)Pause容器

  • Pause容器,又叫Infra容器
  • k8s中定义pause容器: KUBELET_POD_INFRA_CONTAINER=–pod-infra-container-image=gcr.io/google_containers/pause-amd64:3.0
  • 作用:
    • 在pod中担任Linux命名空间共享的基础;
    • 启用pid命名空间,开启init进程。
    • 在pod内部所有容器间共享volume存储等

####测试例子:

# 使用docker启动一个pause容器
docker run -d --name pause -p 8880:80 jimmysong/pause-amd64:3.0# 使用pause容器的网络、ipc、pid创建一个nginx容器
$ cat <<EOF >> nginx.conff
error_log stderr;
events { worker_connections  1024; }
http {access_log /dev/stdout combined;server {listen 80 default_server;server_name example.com www.example.com;location / {proxy_pass http://127.0.0.1:2368;}}
}
EOF
docker run -d --name nginx -v `pwd`/nginx.conf:/etc/nginx/nginx.conf --net=container:pause --ipc=container:pause --pid=container:pause nginx# 创建一个ghost的容器
docker run -d --name ghost --net=container:pause --ipc=container:pause --pid=container:pause ghost

注意:

  • 三个容器公用一个namespace,容器之间使用localhost访问

(4)容器生命周期

参考:https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/
Pod的状态status信息保存在PodStatus的phase字段内:

  • 挂起(pending)
  • 运行中(running)
  • 成功(succeeded)
  • 失败(failed)
  • 未知(unknown)

PodConditions

  • lastProbeTime
  • lastTransitionTime
  • message
  • reason
  • status
    • PodScheduled:Pod被调度到node
    • Ready:Pod可以提供服务,可以加入负载均衡
    • Initialized:init容器执行完毕
    • Unschedulable:比如资源不足
    • ContainersReady:所用容器准备好了

a、容器探针

容器的探针是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的Handler。有三种类型的处理程序:

  • ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
  • TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
  • HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于200 且小于 400,则诊断被认为是成功的。

每次探测都将获得以下三种结果之一:

  • 成功:容器通过了诊断。
  • 失败:容器未通过诊断。
  • 未知:诊断失败,因此不会采取任何行动。

Kubelet 可以选择是否执行在容器上运行的两种探针执行和做出反应:

  • livenessProbe:指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其 重启策略 的影响。如果容器不提供存活探针,则默认状态为 Success。
  • readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为 Success。

b、livenessProbe和readinessProbe使用场景

  • 如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针; kubelet 将根据 Pod 的restartPolicy 自动执行正确的操作。
  • 如果希望容器在探测失败时被杀死并重新启动,那么请指定一个存活探针,并指定restartPolicy 为 Always 或 OnFailure。
  • 如果要仅在探测成功时才开始向 Pod 发送流量,请指定就绪探针。在这种情况下,就绪探针可能与存活探针相同,但是 spec 中的就绪探针的存在意味着 Pod 将在没有接收到任何流量的情况下启动,并且只有在探针探测成功后才开始接收流量。
  • 如果您希望容器能够自行维护,您可以指定一个就绪探针,该探针检查与存活探针不同的端点。
  • 请注意,如果您只想在 Pod 被删除时能够排除请求,则不一定需要使用就绪探针;在删除 Pod 时,Pod 会自动将自身置于未完成状态,无论就绪探针是否存在。当等待 Pod 中的容器停止时,Pod 仍处于未完成状态

问题:
就绪探针和SOA框架中的dubbo-admin中的策略是否有冲突呢?还是相辅相成???上线之前一定要测试。

c、Pod重启策略及调度

  • PodSpec 中有一个 restartPolicy 字段,可能的值为 Always、OnFailure 和 Never。默认为 Always。
  • Pod不会消失,直到人为销毁;一般不会直接使用Pod,使用方式如下:
    • 使用job执行预计会终止的Pod,如,批量计算,仅适用于重启策略为OnFailure或Never的Pod
    • 对预计不会终止的 Pod 使用 ReplicationController、ReplicaSet 和 Deployment,restartPolicy 一般为 Always
    • DaemonSet一般用于日志等服务

d、配置项:参考相关接口文档

livenessProbe对象:

  • failureThreshold 探测几次失败 才算失败 默认是连续三次
  • periodSeconds 每次的多长时间探测一次 默认10s
  • timeoutSeconds 探测超时的秒数 默认1s
  • initialDelaySeconds 初始化延迟探测,第一次探测的时候,因为主程序未必启动完成
  • tcpSocket 检测端口的探测
    • port:1-65535
  • exec command 的方式探测 例如 ps 一个进程
    • command
  • httpGet http请求探测
    • path
    • port
    • host
    • scheme:
    • httpHeaders
      • name
      • value

官网的例子:

apiVersion: v1
kind: Pod
metadata:labels:test: livenessname: liveness-http
spec:containers:- args:- /serverimage: k8s.gcr.io/livenesslivenessProbe:httpGet:# when "host" is not defined, "PodIP" will be used# host: my-host# when "scheme" is not defined, "HTTP" scheme will be used. Only "HTTP" and "HTTPS" are allowed# scheme: HTTPSpath: /healthzport: 8080httpHeaders:- name: X-Custom-Headervalue: AwesomeinitialDelaySeconds: 15timeoutSeconds: 1name: liveness

注意多容器的Pod的状态

(5)pod hook测试

说明:

  • Kubernetes在容器创建之后就会马上发送postStart事件,但是并没法保证一定会 这么做,它会在容器入口被调用之前调用postStart操作,因为postStart的操作跟容器的操作是异步的,而且Kubernetes控制台会锁住容器直至postStart完成,因此容器只有在 postStart操作完成之后才会被设置成为RUNNING状态。
  • Kubernetes在容器结束之前发送preStop事件,并会在preStop操作完成之前一直锁住容器 状态,除非Pod的终止时间过期了。

官方yaml:

apiVersion: v1
kind: Pod
metadata:name: lifecycle-demo
spec:containers:- name: lifecycle-demo-containerimage: nginxlifecycle:postStart:exec:command: ["/bin/sh", "-c", "echo Hello > /usr/share/message"]preStop:exec:command: ["/usr/sbin/nginx","-s","quit"]

修改yaml文件

apiVersion: v1
kind: Pod
metadata:name: lifecycle-demo
spec:containers:- name: lifecycle-demo-containerimage: nginxlifecycle:postStart:exec:command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]preStop:exec:command: ["/bin/sleep","20"]

注意:

  • 此镜像中sleep的路径是/bin/sleep
  • pod默认的delete超时时间应该是30s。因此这里设置slee是20s

操作步骤:

[root@server ~]# kubectl create -f lifecycle-events.yaml
[root@server ~]# kubectl get pod/lifecycle-demo
NAME             READY     STATUS    RESTARTS   AGE
lifecycle-demo   1/1       Running   0          15s
[root@server ~]# kubectl describe pod/lifecycle-demo
......
Node:               client02/10.40.2.229
......
Status:             Running
IP:                 172.17.1.7
......
[root@server apis]# time kubectl delete -f lifecycle-events.yaml
pod "lifecycle-demo" deletedreal  0m21.562s
user    0m0.205s
sys 0m0.046s# 在pod执行sleep期间,执行curl命令,结果是正常的
[root@client02 k8s-v1.11.5]# curl  http://172.17.1.7
<!DOCTYPE html>
......注意:
(1)当设置多个preStop时报错:
preStop:exec:command: ["/bin/sleep","20"]httpGet:port: httppath: /index.html
The Pod "lifecycle-demo" is invalid: spec.containers[0].lifecycle.preStop.httpGet: Forbidden: may not specify more than 1 handler type(2)多个相同的preStop会覆盖
preStop:exec:command: ["/bin/sleep","20"]command: ["/bin/sleep","5"]   #会将sleep 20覆盖掉(3)当preStop的命令出错时会直接忽略
比如:
preStop:exec:command: ["/bin/sleep 20 && /bin/sleep 5"]或者command: ["/bin/sleep", "20", "&&", "/bin/sleep", "5"]

遗留问题:

  • preStop中执行多个命令?

    • command: ["/bin/sh","-c",“sleep 20 && sleep 20”]
  • 怎么在yaml中修改这个默认30s呢?
    • 抽空去翻一下它的接口文件再来更新文档,用命令行的参数好像可以指定

(6)Pod Preset

使用场景:

  • 让一批容器在启动的时候就注入一些信息,比如 secret、volume、volume mount 和环境变量,而又不想一个一个的改这些 Pod 的 template
  • 同一套代码,用来做不同的服务,制作镜像时是同一个dockfile,但是跑的是不同的角色,个人觉得非常合适,当然,也可以直接通过这个对Pod注入环境变量
  • 某个Pod中不想任何Preset的干扰,使用:podpreset.admission.kubernetes.io/exclude:“true”

配置参考:https://kubernetes.io/docs/tasks/inject-data-application/podpreset/

(7)Disruptions(中断)及PodDisruptionBudget(PDB,中断预算)

自愿中断和非自愿中断

非自愿性中断,出现不可避免的硬件或系统软件错误例如:

  • 后端节点物理机的硬件故障
  • 集群管理员错误地删除虚拟机(实例)
  • 云提供商或管理程序故障使虚拟机消失
  • 内核恐慌(kernel panic)
  • 节点由于集群网络分区而从集群中消失
  • 由于节点资源不足而将容器逐出

自愿性中断:应用程序所有者发起的操作和由集群管理员发起的操作:

  • 典型的应用程序所有者操作包括:

    • 删除管理该 pod 的 Deployment 或其他控制器
    • 更新了 Deployment 的 pod 模板导致 pod 重启
    • 直接删除 pod(意外删除)
  • 集群管理员操作包括:
    • 排空(drain)节点进行修复或升级。
    • 从集群中排空节点以缩小集群(了解集群自动调节)。
    • 从节点中移除一个 pod,以允许其他 pod 使用该节点。

减轻非自愿性中断的方法:

  • 确保您的 pod 请求所需的资源。
  • 如果您需要更高的可用性,请复制您的应用程序。 (了解有关运行复制的无状态和有状态应用程序的信息。)
  • 为了在运行复制应用程序时获得更高的可用性,请跨机架(使用反亲和性)或跨区域(如果使用多区域集群)分布应用程序。

pdb

  • PDB 将限制在同一时间自愿中断的复制应用程序中宕机的 Pod 的数量。例如,基于定额的应用程序希望确保运行的副本数量永远不会低于仲裁所需的数量。
  • 排空节点kubectl drain NODENAME;重新加入schedule:kubectl uncordon NODENAME
  • 由于应用程序的滚动升级而被删除或不可用的 Pod 确实会计入中断预算,但控制器(如 Deployment 和 StatefulSet)在进行滚动升级时不受 PDB 的限制

相关连接:

  • 设置PDB:https://kubernetes.io/docs/tasks/run-application/configure-pdb/
  • 排空节点:https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/
  • 亲和性相关:https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
  • pdb官方说明的例子:https://kubernetes.io/docs/concepts/workloads/pods/disruptions/

(8)一个测试的例子

apiVersion: v1
kind: Pod
metadata:name: nginx-p
spec:containers:- name: nginximage: nginx- name: busyboximage: busyboxcommand: ['sh','-c','sleep 36000']

注意:

  • 如果上面的env-pause容器没有command,会导致真个pod状态为CrashLoopBackOff
  • 使用kubectl exec -it pause-hehe /bin/bash 默认是进入第一个容器
  • 两个容器只是共享namespace、网络、init进程、volume
  • busybox中有许多的命令,当进入lifecycle容器,这些命令并不能使用
  • 使用kubectl exec -it -p nginx-p -c busybox /bin/sh进入指定容器
  • 奇怪的是:
    / # netstat -tnlp
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
    / # telnet localhost 80
    Connection closed by foreign host
    

4、零碎的东西

(1)标签:

  • 给对象设置标签(k、v类型)
  • 基于标签选择器,操作相关对象
  • 语法:
    • Label key的组成:

      • 不得超过63个字符
      • 可以使用前缀,使用/分隔,前缀必须是DNS子域,不得超过253个字符,系统中的自动化组件创建的label必须指定前缀,kubernetes.io/由kubernetes保留
      • 起始必须是字母(大小写都可以)或数字,中间可以有连字符、下划线和点
    • Label value的组成:
      • 不得超过63个字符
      • 起始必须是字母(大小写都可以)或数字,中间可以有连字符、下划线和点
    • Label selector有两种类型:
      • equality-based :可以使用=、==、!=操作符,可以使用逗号分隔多个表达式
      • set-based :可以使用in、notin、!操作符,另外还可以没有操作符,直接写出某个label的key,表示过滤有某个key的object而不管该key的value是何值,!表示没有该label的object
  • 实例:
    $ kubectl get pods -l environment=production,tier=frontend
    $ kubectl get pods -l 'environment in (production),tier in (frontend)'在service、replicationcontroller等object中有对pod的label selector,使用方法只能使用等于操作,例如:
    selector:component: redis在Job、Deployment、ReplicaSet和DaemonSet这些object中,支持set-based的过滤,例如:
    selector:matchLabels:component: redismatchExpressions:- {key: tier, operator: In, values: [cache]}- {key: environment, operator: NotIn, values: [dev]}
    如Service通过label selector将同一类型的pod作为一个服务expose出来。另外在node affinity和pod affinity中的label selector的语法又有些许不同,示例如下:affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/e2e-az-nameoperator: Invalues:- e2e-az1- e2e-az2preferredDuringSchedulingIgnoredDuringExecution:- weight: 1preference:matchExpressions:- key: another-node-label-keyoperator: Invalues:- another-node-label-value
    

(2)annotation(注解)

  • Label和Annotation都可以将元数据关联到Kubernetes资源对象。Label主要用于选择对象,可以挑选出满足特定条件的对象。相比之下,annotation 不能用于标识及选择对象。annotation中的元数据可多可少,可以是结构化的或非结构化的,也可以包含label中不允许出现的字符。两者都是key/value类型
  • 信息举例:
    • 声明配置层管理的字段。使用annotation关联这类字段可以用于区分以下几种配置来源:客户端或服务器设置的默认值,自动生成的字段或自动生成的 auto-scaling 和 auto-sizing 系统配置的字段。
    • 创建信息、版本信息或镜像信息。例如时间戳、版本号、git分支、PR序号、镜像哈希值以及仓库地址。
    • 记录日志、监控、分析或审计存储仓库的指针
    • 可以用于debug的客户端(库或工具)信息,例如名称、版本和创建信息。
    • 用户信息,以及工具或系统来源信息、例如来自非Kubernetes生态的相关对象的URL信息。
    • 轻量级部署工具元数据,例如配置或检查点。
    • 负责人的电话或联系方式,或能找到相关信息的目录条目信息,例如团队网站。
  • 例子:
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:name: istio-manager
    spec:replicas: 1template:metadata:annotations:alpha.istio.io/sidecar: ignorelabels:istio: managerspec:serviceAccountName: istio-manager-service-accountcontainers:- name: discoveryimage: harbor-001.jimmysong.io/library/manager:0.1.5imagePullPolicy: Alwaysargs: ["discovery", "-v", "2"]ports:- containerPort: 8080env:- name: POD_NAMESPACEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.namespace- name: apiserverimage: harbor-001.jimmysong.io/library/manager:0.1.5imagePullPolicy: Alwaysargs: ["apiserver", "-v", "2"]ports:- containerPort: 8081env:- name: POD_NAMESPACEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.namespace
    

(3)Taint和Toleration(污点和容忍)

参考:https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/

pod和node之间调度关系方式:

  • 给 node 节点设置 label,通过给 pod 设置 nodeSelector 将 pod 调度到具有匹配标签的节点上。
  • 亲和性(pod和节点相吸)
  • taint(pod和节点相斥)和toleration配合
# 设置taint:
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule
# 取消taint:
kubectl taint nodes node1 key1:NoSchedule-
kubectl taint nodes node1 key1:NoExecute-
# 查看taint
kubectl describe nodes node1# 配合toleration:——在 pod 的 spec 中设置 tolerations 字段
tolerations:
- key: "key1"operator: "Equal"value: "value1"effect: "NoSchedule"
- key: "key1"operator: "Equal"value: "value1"effect: "NoExecute"tolerationSeconds: 6000# value 的值可以为 NoSchedule、PreferNoSchedule 或 NoExecute。
# operator的值可以是Equal和Exists
# tolerationSeconds 是当 pod 需要被驱逐时,可以继续在 node 上运行的时间。

Taint based Evictions(驱逐)and Condition:

  • 理解

    • 基于节点的一些状况来定义tolerations,这些状况相当于是k8s系统为我们定义好的,而taint是我们自己定义的
  • evictions:
    • node.kubernetes.io/not-ready: Node is not ready. This corresponds to the NodeCondition Ready being “False”.
    • node.kubernetes.io/unreachable: Node is unreachable from the node controller. This corresponds to the NodeCondition Ready being “Unknown”.
    • node.kubernetes.io/out-of-disk: Node becomes out of disk.
    • node.kubernetes.io/memory-pressure: Node has memory pressure.
    • node.kubernetes.io/disk-pressure: Node has disk pressure.
    • node.kubernetes.io/network-unavailable: Node’s network is unavailable.
    • node.kubernetes.io/unschedulable: Node is unschedulable.
    • node.cloudprovider.kubernetes.io/uninitialized: When the kubelet is started with “external” cloud provider, this taint is set on a node to mark it as unusable. After a controller from the cloud-controller-manager initializes this node, the kubelet removes this taint.
  • conditions:
    • node.kubernetes.io/memory-pressure
    • node.kubernetes.io/disk-pressure
    • node.kubernetes.io/out-of-disk (only for critical pods)
    • node.kubernetes.io/unschedulable (1.10 or later)
    • node.kubernetes.io/network-unavailable (host network only)
tolerations:
- key: "node.kubernetes.io/unreachable"operator: "Exists"effect: "NoExecute"tolerationSeconds: 6000

(4)亲和性、反亲和性

(5)Horizontal Pod Autoscaling(水平自动扩展)

https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/

(6)Garbage Collection(垃圾收集器)

参考:

  • https://jimmysong.io/kubernetes-handbook/concepts/garbage-collection.html
  • https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/
    在滚动升级、停止pod等时,能否将镜像给删掉

下面两个东西,想放到概念的最后面来研究

5、controller(控制器)

(1)ReplicaSet

  • ReplicaSet 和 Replication Controller之间的唯一区别是对选择器的支持。ReplicaSet支持labels user guide中描述的set-based选择器要求, 而Replication Controller仅支持equality-based的选择器要求
  • 官方建议使用Deployment完成所有ReplicatSet的功能
  • Writing a ReplicaSet Spec
    • 查看Deployment的对应内容即可

(2)Deployment

参考:

  • k8s中文社区文档:http://docs.kubernetes.org.cn/317.html
  • 官方文档:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

a、简介

  • Deployment为Pod和Replica Set(升级版的 Replication Controller)提供声明式更新。
  • 只需要在 Deployment 中描述您想要的目标状态是什么,Deployment controller 就会帮您将 Pod 和ReplicaSet 的实际状态改变到您的目标状态。您可以定义一个全新的 Deployment 来创建 ReplicaSet 或者删除已有的 Deployment 并创建一个新的来替换
  • 不要手动管理由 Deployment 创建的 Replica Set
    • 使用Deployment来创建ReplicaSet。ReplicaSet在后台创建pod。检查启动状态,看它是成功还是失败。
    • 然后,通过更新Deployment的PodTemplateSpec字段来声明Pod的新状态。这会创建一个新的ReplicaSet,Deployment会按照控制的速率将pod从旧的ReplicaSet移动到新的ReplicaSet中。
    • 如果当前状态不稳定,回滚到之前的Deployment revision。每次回滚都会更新Deployment的revision。
    • 扩容Deployment以满足更高的负载。
    • 暂停Deployment来应用PodTemplateSpec的多个修复,然后恢复上线。
    • 根据Deployment 的状态判断上线是否hang住了。
    • 清除旧的不必要的 ReplicaSet。

b、创建Deployment

apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deploymentlabels:app: nginx   # 这是Deployment的标签
spec:replicas: 3selector:matchLabels:app: nginx    #这个必须和下面模板的标签匹配起来template:metadata:labels:app: nginx   #这才是模板的标签spec:containers:- name: nginximage: nginx:1.7.9  # 被我改成了1.7.9,官方是1.15.4ports:- containerPort: 80

例子说明:

  • 一个被.metadata.name字段标示的叫做nginx-deployment的Deployment被创建
  • 这个Deployment用replicas字段创建了3个pod的副本
  • selector(选择器)字段定义了Deployment管理那些pods,此例,你简单的选择了一个标签被Pod template定义的app: nginx;当然,更多复杂的选择规则可以用,只要Pod template适合这些规则
  • template字段包含的子字段:
    • 这些pods使用labels设置了标签:app: nginx
    • Pod模板说明, 或者.template.spec字段标示这个pod跑一个Docker hub 的nginx容器,版本是1.5.4
    • 创建一个容器,并且名字用name字段定义为nginx
    • 跑nginx镜像,版本是1.5.4
    • 打开80端口一遍容器可以发送或者接收流量

创建Deployment,执行下面的命令(我已经下载到了本地):

kubectl create -f nginx-deployment.yaml --record=true
# 注意:
# 将kubectl的 --record 的 flag 设置为 true可以在 annotation 中记录当前命令创建或者升级了该资源。
# 这在未来会很有用,例如,查看在每个 Deployment revision 中执行了哪些命令[root@server apis]# kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           3s
字段说明:
UP-TO-DATE:显示已经升级到期望的状态的副本数[root@server apis]# kubectl get pods -o wide
NAME                                READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
nginx-deployment-67594d6bf6-2l44w   1/1       Running   0          13s       172.17.2.21   client01   <none>
nginx-deployment-67594d6bf6-8tqbj   1/1       Running   0          13s       172.17.2.22   client01   <none>
nginx-deployment-67594d6bf6-wgddz   1/1       Running   0          13s       172.17.1.21   client02   <none>
nginx-p                             2/2       Running   0          5h        172.17.1.19   client02   <none>[root@server apis]# kubectl get pods -o wide --show-labels
NAME                                READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE   LABELS
nginx-deployment-67594d6bf6-2l44w   1/1       Running   0          53s       172.17.2.21   client01   <none>           app=nginx,pod-template-hash=2315082692
nginx-deployment-67594d6bf6-8tqbj   1/1       Running   0          53s       172.17.2.22   client01   <none>           app=nginx,pod-template-hash=2315082692
nginx-deployment-67594d6bf6-wgddz   1/1       Running   0          53s       172.17.1.21   client02   <none>           app=nginx,pod-template-hash=2315082692
nginx-p                             2/2       Running   0          5h        172.17.1.19   client02   <none>           <none>[root@server apis]# kubectl rollout status deployment.v1.apps/nginx-deployment
deployment "nginx-deployment" successfully rolled out[root@server apis]# kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-67594d6bf6   3         3         3         8m
注意:
Notice that the name of the ReplicaSet is always formatted as [DEPLOYMENT-NAME]-[POD-TEMPLATE-HASH-VALUE].
The hash value is automatically generated when the Deployment is created.

注意:

  • 必须在 Deployment 中的 selector 指定正确的 pod template label(在该示例中是 app = nginx),不要跟其他的 controller 的 selector 中指定的 pod template label 搞混了(包括 Deployment、Replica Set、Replication Controller 等)。Kubernetes 本身并不会阻止您任意指定 pod template label ,但是如果您真的这么做了,这些 controller 之间会互相冲突,并可能导致不希望的行为。

c、升级Deployment

注意: Deployment 的 rollout 当且仅当 Deployment 的 pod template(例如.spec.template)中的label更新或者镜像更改时被触发。其他更新,例如扩容Deployment不会触发 rollout。

[root@server apis]# kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.9.1 --record && kubectl rollout status deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment image updated
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-deployment" successfully rolled out
[root@server apis]#  kubectl get deployments
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           24m[root@server apis]# kubectl rollout status deployment.v1.apps/nginx-deployment
deployment "nginx-deployment" successfully rolled out[root@server apis]# kubectl get pods -o wide --show-labels
NAME                                READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE   LABELS
nginx-deployment-6fdbb596db-4zqwq   1/1       Running   0          37s       172.17.2.24   client01   <none>           app=nginx,pod-template-hash=2986615286
nginx-deployment-6fdbb596db-l44hp   1/1       Running   0          40s       172.17.2.23   client01   <none>           app=nginx,pod-template-hash=2986615286
nginx-deployment-6fdbb596db-qlfhq   1/1       Running   0          38s       172.17.1.22   client02   <none>           app=nginx,pod-template-hash=2986615286
nginx-p                             2/2       Running   0          5h        172.17.1.19   client02   <none>           <none>[root@server apis]# kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-67594d6bf6   0         0         0         25m
nginx-deployment-6fdbb596db   3         3         3         55滚动升级策略:
RollingUpdateStrategy:
25% max unavailable:每次最多25%的pod不可用
25% max surge:每次最多25%的新pod启动
kubectl describe deployments
......
Events:Type    Reason             Age   From                   Message----    ------             ----  ----                   -------Normal  ScalingReplicaSet  40m   deployment-controller  Scaled up replica set nginx-deployment-67594d6bf6 to 3Normal  ScalingReplicaSet  16m   deployment-controller  Scaled up replica set nginx-deployment-6fdbb596db to 1Normal  ScalingReplicaSet  16m   deployment-controller  Scaled down replica set nginx-deployment-67594d6bf6 to 2Normal  ScalingReplicaSet  16m   deployment-controller  Scaled up replica set nginx-deployment-6fdbb596db to 2Normal  ScalingReplicaSet  16m   deployment-controller  Scaled down replica set nginx-deployment-67594d6bf6 to 1Normal  ScalingReplicaSet  16m   deployment-controller  Scaled up replica set nginx-deployment-6fdbb596db to 3Normal  ScalingReplicaSet  16m   deployment-controller  Scaled down replica set nginx-deployment-67594d6bf6 to 0
分析:
第一步是创建的时候,直接将副本扩容到3
第二步是将新的rs扩容到1,同时将旧的rs副本降到2
第三步是将新的rs扩容到2,同时将旧的rs副本降到1
第四步是将新的rs扩容到3,同时将旧的rs副本降到0第二种升级方式:
kubectl edit deployment.v1.apps/nginx-deployment
.spec.template.spec.containers[0].image from nginx:1.9.1 to nginx:1.15.4# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: apps/v1
kind: Deployment
metadata:annotations:deployment.kubernetes.io/revision: "2"kubernetes.io/change-cause: kubectl set image deployment.v1.apps/nginx-deploymentnginx=nginx:1.9.1 --record=truecreationTimestamp: 2018-12-14T06:47:03Zgeneration: 2labels:app: nginxname: nginx-deploymentnamespace: defaultresourceVersion: "837495"selfLink: /apis/apps/v1/namespaces/default/deployments/nginx-deploymentuid: 108fefd9-ff6c-11e8-92e8-005056b6756e
spec:progressDeadlineSeconds: 600replicas: 3revisionHistoryLimit: 2selector:matchLabels:app: nginxstrategy:rollingUpdate:maxSurge: 25%maxUnavailable: 25%type: RollingUpdatetemplate:metadata:creationTimestamp: nulllabels:app: nginxspec:containers:- image: nginx:1.9.1imagePullPolicy: IfNotPresentname: nginxports:- containerPort: 80protocol: TCPresources: {}terminationMessagePath: /dev/termination-logterminationMessagePolicy: FilednsPolicy: ClusterFirstrestartPolicy: AlwaysschedulerName: default-schedulersecurityContext: {}terminationGracePeriodSeconds: 30
status:availableReplicas: 3conditions:- lastTransitionTime: 2018-12-14T06:47:04ZlastUpdateTime: 2018-12-14T06:47:04Zmessage: Deployment has minimum availability.reason: MinimumReplicasAvailablestatus: "True"type: Available- lastTransitionTime: 2018-12-14T06:47:03ZlastUpdateTime: 2018-12-14T07:11:29Zmessage: ReplicaSet "nginx-deployment-6fdbb596db" has successfully progressed.reason: NewReplicaSetAvailablestatus: "True"type: ProgressingobservedGeneration: 2readyReplicas: 3replicas: 3updatedReplicas: 3
注意:直接这样修改,使用kubectl describe deployments查看的时候,发现event并没有更新,模板也没有更新

注意(不去逐条翻译原文了):

  • 当一个deployment正在rollout中,一个版本更新操作又来了,会立即停掉rollout任务,并将版本按既定规则更新
  • label selector更新(官方不建议如下操作,建议提前规划好)
    • 增加:增添 selector 需要同时在 Deployment 的 spec 中更新新的 label,否则将返回校验错误。此更改是不可覆盖的,这意味着新的 selector 不会选择使用旧 selector 创建的 ReplicaSet 和 Pod,从而导致所有旧版本的 ReplicaSet 都被丢弃,并创建新的 ReplicaSet。
    • 更新:即更改 selector key 的当前值,将导致跟增添 selector 同样的后果。
    • 删除:即删除 Deployment selector 中的已有的 key,不需要对 Pod template label 做任何更改,现有的 ReplicaSet 也不会成为孤儿,但是请注意,删除的 label 仍然存在于现有的 Pod 和 ReplicaSet 中。

d、回滚deployment

  • 只要 Deployment 的 rollout 被触发就会创建一个 revision。也就是说当且仅当 Deployment 的 Pod template(如.spec.template)被更改,例如更新template 中的 label 和容器镜像时,就会创建出一个新的 revision
  • 默认情况下,kubernetes 会在系统中保存前两次的 Deployment 的 rollout 历史记录,以便您可以随时回退(您可以修改.spec.revisionHistoryLimit: 2来更改保存的revision数)
  • 其他的更新,比如扩容 Deployment 不会创建 revision——因此我们可以很方便的手动或者自动扩容。这意味着当您回退到历史 revision 是,只有 Deployment 中的 Pod template 部分才会回退。

比如将nginx的版本1.9.1写成了1.91

[root@server apis]# kubectl edit deployment.v1.apps/nginx-deployment
Edit cancelled, no changes made.rollout将会卡住
[root@server apis]# kubectl rollout status deployments nginx-deployment
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...新的rs在启动一个pod,但是卡住了,旧的rs中3个pod仍然在
[root@server soft]# kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-58c7645486   1         1         0         1m
nginx-deployment-67594d6bf6   0         0         0         2h
nginx-deployment-6fdbb596db   3         3         3         1h[root@server soft]# kubectl get pods
NAME                                READY     STATUS             RESTARTS   AGE
nginx-deployment-58c7645486-gvgsm   0/1       ImagePullBackOff   0          2m
nginx-deployment-6fdbb596db-4zqwq   1/1       Running            0          1h
nginx-deployment-6fdbb596db-l44hp   1/1       Running            0          1h
nginx-deployment-6fdbb596db-qlfhq   1/1       Running            0          1h★注意:
eployment controller会自动停止坏的 rollout,并停止扩容新的 ReplicaSet[root@server soft]# kubectl describe deployment
......
OldReplicaSets:  nginx-deployment-6fdbb596db (3/3 replicas created)
NewReplicaSet:   nginx-deployment-58c7645486 (1/1 replicas created)
Events:Type    Reason             Age   From                   Message----    ------             ----  ----                   -------Normal  ScalingReplicaSet  5m    deployment-controller  Scaled up replica set nginx-deployment-58c7645486 to 1并没有将1.91的打印出来???
[root@server soft]# kubectl rollout history deployment/nginx-deployment
deployments "nginx-deployment"
REVISION  CHANGE-CAUSE
1         kubectl create --filename=nginx-deployment.yaml --record=true
2         kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.15.4 --record=true
3         kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.15.4 --record=true但是详细信息中写出来了
[root@server soft]# kubectl rollout history deployment/nginx-deployment --revision=3
deployments "nginx-deployment" with revision #3
Pod Template:Labels:    app=nginxpod-template-hash=1473201042Annotations: kubernetes.io/change-cause=kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.15.4 --record=trueContainers:nginx:Image: nginx:1.91Port: 80/TCPHost Port:    0/TCPEnvironment:   <none>Mounts: <none>Volumes:    <none>
注意:
CHANGE-CAUSE 是创建时从Deployment的注解中拷贝过来,kubernetes.io/change-cause,可以手动指定
kubectl annotate deployment.v1.apps/nginx-deployment kubernetes.io/change-cause="image updated to 1.9.1"回滚到前一个版本:
[root@server soft]# kubectl rollout undo deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment[root@server soft]# kubectl rollout undo deployment.v1.apps/nginx-deployment
deployment.apps/nginx-deployment
[root@server soft]# kubectl describe deployment
......
Conditions:Type           Status  Reason----           ------  ------Available      True    MinimumReplicasAvailableProgressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-6fdbb596db (3/3 replicas created)
Events:Type    Reason              Age   From                   Message----    ------              ----  ----                   -------Normal  ScalingReplicaSet   19m   deployment-controller  Scaled up replica set nginx-deployment-58c7645486 to 1Normal  DeploymentRollback  7s    deployment-controller  Rolled back deployment "nginx-deployment" to revision 2Normal  ScalingReplicaSet   7s    deployment-controller  Scaled down replica set nginx-deployment-58c7645486 to 0
[root@server soft]# kubectl get deployment nginx-deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           2h
[root@server soft]# kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-58c7645486   0         0         0         19m
nginx-deployment-67594d6bf6   0         0         0         2h
nginx-deployment-6fdbb596db   3         3         3         2h回滚到指定版本,注意最好看看详情
kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision=2

注意:

  • 上面的例子是在pull镜像时卡住了,导致滚动升级停止
  • 生产上要好好设计就绪探针,防止滚动升级让滚动出错就停止,还是手动的pause deployment???

e、Deployment 扩容

[root@server soft]# kubectl scale deployment.v1.apps/nginx-deployment --replicas=5
deployment.apps/nginx-deployment scaled
[root@server soft]# kubectl get deployment nginx-deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   5         5         5            5           2h假设您的集群中启用了horizontal pod autoscaling,您可以给 Deployment 设置一个 autoscaler,基于当前 Pod的 CPU 利用率选择最少和最多的 Pod 数。
[root@server soft]# kubectl autoscale deployment.v1.apps/nginx-deployment --min=5 --max=7 --cpu-percent=80
horizontalpodautoscaler.autoscaling/nginx-deployment autoscaled
[root@server soft]# kubectl get deployment nginx-deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   5         5         5            5           2h比例扩容
RollingUpdate Deployment 支持同时运行一个应用的多个版本。
或者 autoscaler 扩 容 RollingUpdate Deployment 的时候,正在中途的 rollout(进行中或者已经暂停的),
为了降低风险,Deployment controller 将会平衡已存在的活动中的 ReplicaSet(有 Pod 的 ReplicaSet)和新加入的 replica。这被称为比例扩容。例如,您正在运行中含有10个 replica 的 Deployment。maxSurge=3,maxUnavailable=2。$ kubectl get deploy
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     10        10        10           10          50s
您更新了一个镜像,而在集群内部无法解析。$ kubectl set image deploy/nginx-deployment nginx=nginx:sometag
deployment "nginx-deployment" image updated
镜像更新启动了一个包含ReplicaSet nginx-deployment-1989198191的新的rollout,
但是它被阻塞了,因为我们上面提到的maxUnavailable。$ kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   5         5         0         9s
nginx-deployment-618515232    8         8         8         1m
然后发起了一个新的Deployment扩容请求。autoscaler将Deployment的repllica数目增加到了15个。
Deployment controller需要判断在哪里增加这5个新的replica。
如果我们没有使用比例扩容,所有的5个replica都会加到一个新的ReplicaSet中。如果使用比例扩容,新添加的replica将传播到所有的ReplicaSet中。大的部分加入replica数最多的ReplicaSet中,小的部分加入到replica数少的ReplciaSet中。0个replica的ReplicaSet不会被扩容。在我们上面的例子中,3个replica将添加到旧的ReplicaSet中,2个replica将添加到新的ReplicaSet中。
rollout进程最终会将所有的replica移动到新的ReplicaSet中,假设新的replica成为健康状态。$ kubectl get deploy
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment     15        18        7            8           7m
$ kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-1989198191   7         7         0         7m
nginx-deployment-618515232    11        11        11        7m

f、暂停和恢复Deployment

注意:

如果水平扩容打开了,升级时一定要小心,后面水平扩容期间再测试
感觉这个暂停和恢复功能主要用来做事件汇总(或者统一)处理的

# 清空一开始弄的nginx-deployment的deployment,重新建立,一定要确定删除了。
# 暂停deployment,只是停止了它的升级,并没有停止它对方提供服务的功能
[root@server apis]# kubectl rollout pause deployment/nginx-deployment
deployment.extensions/nginx-deployment paused[root@server apis]# kubectl describe deployment/nginx-deployment
......
Conditions:Type           Status   Reason----           ------   ------Available      True     MinimumReplicasAvailableProgressing    Unknown  DeploymentPaused
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-6fdbb596db (5/5 replicas created)
......# 更新镜像
[root@server apis]# kubectl set image deploy/nginx-deployment nginx=nginx:1.15.4
deployment.extensions/nginx-deployment image updated[root@server apis]# kubectl describe deployment/nginx-deployment
......
Replicas:               5 desired | 0 updated | 5 total | 5 available | 0 unavailable
......
Conditions:Type           Status   Reason----           ------   ------Available      True     MinimumReplicasAvailableProgressing    Unknown  DeploymentPaused
OldReplicaSets:  nginx-deployment-6fdbb596db (5/5 replicas created)
NewReplicaSet:   <none>
......# 更新资源限制
[root@server apis]# kubectl set resources deployment nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
deployment.extensions/nginx-deployment resource requirements updated# 恢复deployment
[root@server apis]# kubectl rollout resume deployment nginx-deployment
deployment.extensions/nginx-deployment resumed[root@server apis]# kubectl describe deployment/nginx-deployment
......
Replicas:               5 desired | 3 updated | 7 total | 4 available | 3 unavailable
......
Conditions:Type           Status  Reason----           ------  ------Available      True    MinimumReplicasAvailableProgressing    True    ReplicaSetUpdated
OldReplicaSets:  nginx-deployment-6fdbb596db (4/4 replicas created)
NewReplicaSet:   nginx-deployment-6687fd74d4 (3/3 replicas created)
......# 使用watch的功能,监控replicatSet的变化
# kubectl  get rs -w
......

g、Deployment 状态

kubectl rollout status,可以使用此命令进行监控

  • 进行中的Deployment: progressing

    • Deployment 正在创建新的ReplicaSet过程中。
    • Deployment 正在扩容一个已有的 ReplicaSet。
    • Deployment 正在缩容一个已有的 ReplicaSet。
    • 有新的可用的 pod 出现。
  • 完成的Deployment:complete
    • Deployment 最小可用。最小可用意味着 Deployment 的可用 replica 个数等于或者超过 Deployment 策略中的期望个数。
    • 所有与该 Deployment 相关的replica都被更新到了您指定版本,也就说更新完成。
    • 该 Deployment 中没有旧的 Pod 存在。
    • 如果 rollout 成功完成,kubectl rollout status将返回一个0值的 Exit Code。
  • 失败的 Deployment:
失败的Deployment:
  • 可能原因:

    • 无效的引用
    • 不可读的 probe failure
    • 镜像拉取错误
    • 权限不够
    • 范围限制
    • 程序运行时配置错误
  • 探测:
    • Deployment spec 中指定spec.progressDeadlineSeconds,表示 Deployment controller 等待多少秒才能确定(通过 Deployment status)Deployment进程是卡住的。
    • kubectl命令设置:kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
      • 当超过截止时间后,Deployment controller 会在 Deployment 的 status.conditions中增加一条DeploymentCondition,它包括如下属性:

        • Type=Progressing
        • Status=False
        • Reason=ProgressDeadlineExceeded
注意:
  • kubernetes除了报告Reason=ProgressDeadlineExceeded状态信息外不会对卡住的 Deployment 做任何操作。更高层次的协调器可以利用它并采取相应行动,例如,回滚 Deployment 到之前的版本。
  • 如果您暂停了一个 Deployment,在暂停的这段时间内kubernetnes不会检查您指定的 deadline。您可以在 Deployment 的 rollout 途中安全的暂停它,然后再恢复它,这不会触发超过deadline的状态。
  • 其他错误:
    $ kubectl describe deployment nginx-deployment
    <...>
    Conditions:Type            Status  Reason----            ------  ------Available       True    MinimumReplicasAvailableProgressing     True    ReplicaSetUpdatedReplicaFailure  True    FailedCreate
    <...>如果是Deadline,结果会是:
    Conditions:Type            Status  Reason----            ------  ------Available       True    MinimumReplicasAvailableProgressing     False   ProgressDeadlineExceededReplicaFailure  True    FailedCreate$ kubectl get deployment nginx-deployment -o yaml
    status:availableReplicas: 2conditions:......- lastTransitionTime: 2016-10-04T12:25:39ZlastUpdateTime: 2016-10-04T12:25:39Zmessage: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:object-counts, requested: pods=1, used: pods=3, limited: pods=2'reason: FailedCreatestatus: "True"type: ReplicaFailureobservedGeneration: 3replicas: 2unavailableReplicas: 2
    
失败的deployment的操作

所有对完成的 Deployment 的操作都适用于失败的 Deployment。您可以对它扩/缩容,回退到历史版本,您甚至可以多次暂停它来应用 Deployment pod template。

h、清理Policy

您可以设置 Deployment 中的 .spec.revisionHistoryLimit 项来指定保留多少旧的 ReplicaSet。 余下的将在后台被当作垃圾收集。apps/v1beta1版本中默认是3。apps/v1是10。

注意:

将该值设置为0,将导致所有的 Deployment 历史记录都会被清除,该 Deployment 就无法再回退了。

i、编写Deployment Spec

Deployment 也需要apiVersion,kind和metadata,.spec

Pod Template
  • .spec.template 是 .spec中唯一要求的字段。
  • .spec.template 是 pod template. 它跟 Pod有一模一样的schema,除了它是嵌套的并且不需要apiVersion 和 kind字段。
  • 另外为了划分Pod的范围,Deployment中的pod template必须指定适当的label(不要跟其他controller重复了,参考selector)和适当的重启策略。
  • 仅仅.spec.template.spec.restartPolicy 可以设置为 Always , 如果不指定的话这就是默认配置。
Replicas
  • .spec.replicas 是可以选字段,指定期望的pod数量,默认是1。
Selector
  • .spec.selector是可选字段,用来指定 label selector ,圈定Deployment管理的pod范围。
  • 如果被指定, .spec.selector 必须匹配 .spec.template.metadata.labels,否则它将被API拒绝。如果 .spec.selector 没有被指定, .spec.selector.matchLabels 默认是 .spec.template.metadata.labels。
  • 在Pod的template跟.spec.template不同或者数量超过了.spec.replicas规定的数量的情况下,Deployment会杀掉label跟selector不同的Pod

注意:

您不应该再创建其他label跟这个selector匹配的pod,或者通过其他Deployment,或者通过其他Controller,例如ReplicaSet和ReplicationController。否则该Deployment会被把它们当成都是自己创建的。Kubernetes不会阻止您这么做。
如果您有多个controller使用了重复的selector,controller们就会互冲突导致无法预估的结果

Strategy

.spec.strategy 指定新的Pod替换旧的Pod的策略

  • .spec.strategy.type 可以是"Recreate"或者是 “RollingUpdate”。"RollingUpdate"是默认值。
  • .spec.strategy.type==Recreate时,在创建出新的Pod之前会先杀掉所有已存在的Pod。
  • .spec.strategy.type==RollingUpdate时,Deployment使用rolling update 的方式更新Pod 。您可以指定maxUnavailable 和 maxSurge 来控制 rolling update 进程。
    • Max Unavailable

      • .spec.strategy.rollingUpdate.maxUnavailable 是可选配置项,用来指定在升级过程中不可用Pod的最大数量。该值可以是一个绝对值(例如5),也可以是期望Pod数量的百分比(例如10%)。通过计算百分比的绝对值向下取整。如果.spec.strategy.rollingUpdate.maxSurge 为0时,这个值不可以为0。默认值是1。
      • 例如,该值设置成30%,启动rolling update后旧的ReplicatSet将会立即缩容到期望的Pod数量的70%。新的Pod ready后,随着新的ReplicaSet的扩容,旧的ReplicaSet会进一步缩容,确保在升级的所有时刻可以用的Pod数量至少是期望Pod数量的70%。
    • Max Surge
      • .spec.strategy.rollingUpdate.maxSurge 是可选配置项,用来指定可以超过期望的Pod数量的最大个数。该值可以是一个绝对值(例如5)或者是期望的Pod数量的百分比(例如10%)。当MaxUnavailable为0时该值不可以为0。通过百分比计算的绝对值向上取整。默认值是1。
      • 例如,该值设置成30%,启动rolling update后新的ReplicatSet将会立即扩容,新老Pod的总数不能超过期望的Pod数量的130%。旧的Pod被杀掉后,新的ReplicaSet将继续扩容,旧的ReplicaSet会进一步缩容,确保在升级的所有时刻所有的Pod数量和不会超过期望Pod数量的130%。
Progress Deadline Seconds
  • .spec.progressDeadlineSeconds 是可选配置项,用来指定在系统报告Deployment的failed progressing ——表现为resource的状态中type=Progressing、Status=False、 Reason=ProgressDeadlineExceeded前可以等待的Deployment进行的秒数。Deployment controller会继续重试该Deployment。未来,在实现了自动回滚后, deployment controller在观察到这种状态时就会自动回滚。
  • 如果设置该参数,该值必须大于 .spec.minReadySeconds。
Min Ready Seconds
  • .spec.minReadySeconds是一个可选配置项,用来指定没有任何容器crash的Pod并被认为是可用状态的最小秒数。默认是0(Pod在ready后就会被认为是可用状态)。进一步了解什么时候Pod会被认为是ready状态,参考:https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes
Rollback To
  • .spec.rollbackTo 是一个可以选配置项,用来配置Deployment回退的配置。设置该参数将触发回退操作,每次回退完成后,该值就会被清除。
  • .spec.rollbackTo.revision是一个可选配置项,用来指定回退到的revision。默认是0,意味着回退到上一个revision。
Revision History Limit
  • Deployment revision history存储在它控制的ReplicaSets中。
  • .spec.revisionHistoryLimit 是一个可选配置项,用来指定可以保留的旧的ReplicaSet数量。该理想值取决于心Deployment的频率和稳定性。如果该值没有设置的话,默认所有旧的Replicaset或会被保留,将资源存储在etcd中,是用kubectl get rs查看输出。每个Deployment的该配置都保存在ReplicaSet中,然而,一旦您删除的旧的RepelicaSet,您的Deployment就无法再回退到那个revison了。
  • 如果您将该值设置为0,所有具有0个replica的ReplicaSet都会被删除。在这种情况下,新的Deployment rollout无法撤销,因为revision history都被清理掉了。
pause
  • .spec.paused是可以可选配置项,boolean值。用来指定暂停和恢复Deployment。Paused和没有paused的Deployment之间的唯一区别就是,所有对paused deployment中的PodTemplateSpec的修改都不会触发新的rollout。Deployment被创建之后默认是非paused。
nginx-deployment中用kubectl edit获取的配置
spec:progressDeadlineSeconds: 600replicas: 5revisionHistoryLimit: 10selector:matchLabels:app: nginxstrategy:rollingUpdate:maxSurge: 25%maxUnavailable: 25%type: RollingUpdatetemplate:metadata:creationTimestamp: nulllabels:app: nginxspec:......
kubectl patch

所有的资源都可以使用kubectl patch命令结合json或者yaml格式对配置进行动态修改,和命令是一样的,它的原理是通过命令行方式修改kubectl edit

[root@server apis]# kubectl get rs
NAME                          DESIRED   CURRENT   READY     AGE
nginx-deployment-6687fd74d4   5         5         5         4h
nginx-deployment-67594d6bf6   0         0         0         4h
nginx-deployment-6fdbb596db   0         0         0         4h
[root@server apis]# kubectl get pods -o wide
NAME                                READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
nginx-deployment-6687fd74d4-2prb2   1/1       Running   0          4h        172.17.1.39   client02   <none>
nginx-deployment-6687fd74d4-6v942   1/1       Running   0          4h        172.17.1.38   client02   <none>
nginx-deployment-6687fd74d4-gkrqs   1/1       Running   0          4h        172.17.2.38   client01   <none>
nginx-deployment-6687fd74d4-mq6n8   1/1       Running   0          4h        172.17.1.37   client02   <none>
nginx-deployment-6687fd74d4-p2wg2   1/1       Running   0          4h        172.17.2.37   client01   <none>
nginx-p                             2/2       Running   7          3d        172.17.1.19   client02   <none>
[root@server apis]# kubectl describe deployment/nginx-deployment
Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Mon, 17 Dec 2018 09:37:13 +0800
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=3
Selector:               app=nginx
Replicas:               5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:Labels:  app=nginxContainers:nginx:Image:      nginx:1.15.4Port:       80/TCPHost Port:  0/TCPLimits:cpu:        200mmemory:     512MiEnvironment:  <none>Mounts:       <none>Volumes:        <none>
Conditions:Type           Status  Reason----           ------  ------Available      True    MinimumReplicasAvailableProgressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-6687fd74d4 (5/5 replicas created)
Events:          <none>
[root@server apis]# kubectl rollout history deployment/nginx-deployment
deployments "nginx-deployment"
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>[root@server apis]# kubectl patch deployment nginx-deployment -p '{"spec": {"rollbackTo": {"revision": 2}}}'
deployment.extensions/nginx-deployment patched
[root@server apis]# kubectl rollout status deployment/nginx-deployment
deployment "nginx-deployment" successfully rolled out
[root@server apis]# kubectl rollout history deployment/nginx-deployment
deployments "nginx-deployment"
REVISION  CHANGE-CAUSE
1         <none>
3         <none>
4         <none>[root@server apis]# kubectl describe deployment/nginx-deployment
Name:                   nginx-deployment
Namespace:              default
CreationTimestamp:      Mon, 17 Dec 2018 09:37:13 +0800
Labels:                 app=nginx
Annotations:            deployment.kubernetes.io/revision=4
Selector:               app=nginx
Replicas:               5 desired | 5 updated | 5 total | 5 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:Labels:  app=nginxContainers:nginx:Image:        nginx:1.9.1Port:         80/TCPHost Port:    0/TCPEnvironment:  <none>Mounts:       <none>Volumes:        <none>
Conditions:Type           Status  Reason----           ------  ------Available      True    MinimumReplicasAvailableProgressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   nginx-deployment-6fdbb596db (5/5 replicas created)
Events:Type    Reason              Age               From                   Message----    ------              ----              ----                   -------Normal  ScalingReplicaSet   29s (x2 over 4h)  deployment-controller  Scaled up replica set nginx-deployment-6fdbb596db to 2Normal  ScalingReplicaSet   29s (x2 over 4h)  deployment-controller  Scaled up replica set nginx-deployment-6fdbb596db to 3Normal  ScalingReplicaSet   29s (x2 over 4h)  deployment-controller  Scaled up replica set nginx-deployment-6fdbb596db to 4Normal  ScalingReplicaSet   29s (x2 over 4h)  deployment-controller  Scaled up replica set nginx-deployment-6fdbb596db to 5Normal  DeploymentRollback  29s               deployment-controller  Rolled back deployment "nginx-deployment" to revision 2Normal  ScalingReplicaSet   29s               deployment-controller  Scaled down replica set nginx-deployment-6687fd74d4 to 4Normal  ScalingReplicaSet   29s               deployment-controller  Scaled down replica set nginx-deployment-6687fd74d4 to 3Normal  ScalingReplicaSet   29s               deployment-controller  Scaled down replica set nginx-deployment-6687fd74d4 to 2Normal  ScalingReplicaSet   28s               deployment-controller  Scaled down replica set nginx-deployment-6687fd74d4 to 1Normal  ScalingReplicaSet   28s               deployment-controller  Scaled down replica set nginx-deployment-6687fd74d4 to 0

i、实例:金丝雀部署

官网文档

另一个解决方案需要多个标签去区分deployment相同组件的不同版本或者不同配置,普通练习发版一个应用程序的紧挨着前一个版本的新版本金丝雀(通过在prod模板中指定不同的image标签),以便新版本在完全更新前能接受到生产的流量

比如:可以使用track标签去区分不同的版本
主力的:稳定版本可以有一个track标签,它的值是stable

name: frontend
replicas: 3
...
labels:app: guestbooktier: frontendtrack: stable
...
image: gb-frontend:v3

创建一个新版本的guestbook frontend也有track标签,不过值和前面的不同是canary,这样两个集合中的pods不会重叠

name: frontend-canary
replicas: 1
...
labels:app: guestbooktier: frontendtrack: canary
...
image: gb-frontend:v4

而前端的服务(service)可以通过选择两个集合的共同子标签囊括两个集合的副本,这样流量就可以转发到两个程序

selector:app: guestbooktier: frontend

您可以调整接收流量的稳定版本和金丝雀版本的副本数,而一旦(金丝雀版本)是确信的,你可以更新稳定版本到最新金丝雀的版本,同时删除金丝雀

相关命令:

# Deploy a canary
kubectl apply -f deployments/ghost-canary.yaml# Roll out a new version
# Edit deployments/ghost.yaml and update the image:
- name: "ghost"image: "kelseyhightower/ghost:0.7.8"# Update the ghost deployment:
kubectl apply -f deployments/ghost.yaml
测试:

nginx-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deploymentlabels:app: nginx
spec:replicas: 3selector:matchLabels:app: nginxtrack: stabletemplate:metadata:labels:app: nginxtrack: stablespec:containers:- name: nginximage: nginx:1.7.9ports:- containerPort: 80

nginx-deployment-canary.yaml

apiVersion: apps/v1
kind: Deployment
metadata:name: nginx-deployment-canarylabels:app: nginx
spec:replicas: 1selector:matchLabels:app: nginxtrack: canarytemplate:metadata:labels:app: nginxtrack: canaryspec:containers:- name: nginximage: nginx:1.9.1ports:- containerPort: 80

操作历史记录

kubectl create -f nginx-deployment.yaml
kubectl describe deployment/nginx-deployment
kubectl get deployment/nginx-deployment -o json
kubectl apply -f nginx-deployment-canary.yaml
kubectl describe deployment/nginx-deployment-canary
kubectl get rs
kubectl get deployment
kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
kubectl get rs
生产环境金丝雀发布:

当然生产一般不用这种方式,一般用lstio来做流量路由

  • 用两组(两个deployment):

    • canary组只有一个实例,并将pod定位到一批特定的主机,通过日志验证的时候更加方便
    • stable组多个实例,pod随机分配
    • 更新的时候,先通过接口更新canary组,然后验证
  • 用一组(一个deployment,临时的canarydeployment):
    • canary是临时组,更新之前创建,通过接口及python客户端获取“stable”组的配置,然后修改json,创建canary临时组(副本只有一个,增加临时canary的标签),验证完毕,更新“stable”组,验证stable组,最后删除canary临时组
  • 上面两种方式,咋一看,第一种方式操作更加方便,当app数量很多的时候,就必须给canary建立专门的主机池(资源池);而第二种方式,看似复杂,一旦使用python完成自动化改造,操作应该很方便,同时,每个项目用1~2台固定主机做canary的pod就可以了。

发布扩展:
https://www.cnblogs.com/apanly/p/8784096.html
https://www.jianshu.com/p/022685baba7d
http://blog.itpub.net/28624388/viewspace-2158717/

(3)StatefulSet

应用实例参考:https://github.com/kubernetes/contrib/tree/master/statefulsets

(4)DaemonSet

(5)JOB & CronJob

a、TTL Controller for Finished Resources

https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/

6、service

参考:

  • https://kubernetes.io/docs/tasks/access-application-cluster/service-access-application-cluster/
  • https://kubernetes.io/docs/concepts/services-networking/service/

(1)先来个实验再说

创建service

# 运行一个deployment,下面这个镜像还是蛮大的,建议提前下载下来
[root@server apis]# kubectl run hello-world --replicas=2 --labels="run=load-balancer-example" --image=gcr.io/google-samples/node-hello:1.0  --port=8080
deployment.apps/hello-world created[root@server apis]# kubectl get deployment
NAME          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-world   2         2         2            0           2m[root@server apis]# kubectl get pods -o wide
NAME                           READY     STATUS              RESTARTS   AGE       IP        NODE       NOMINATED NODE
hello-world-86cddf59d5-tjjh5   0/1       ContainerCreating   0          1m        <none>    client02   <none>
hello-world-86cddf59d5-z2q7c   0/1       ContainerCreating   0          1m        <none>    client01   <none># 正在拉取镜像
[root@server apis]# kubectl describe pods hello-world-86cddf59d5-z2q7c
......
Events:Type    Reason     Age   From               Message----    ------     ----  ----               -------Normal  Scheduled  2m    default-scheduler  Successfully assigned default/hello-world-86cddf59d5-tjjh5 to client02Normal  Pulling    2m    kubelet, client02  pulling image "gcr.io/google-samples/node-hello:1.0"# Create a Service object that exposes the deployment:
[root@server apis]# kubectl expose deployment hello-world --type=NodePort --name=example-service
service/example-service exposed[root@server apis]# kubectl describe service example-service
Name:                     example-service
Namespace:                default
Labels:                   run=load-balancer-example
Annotations:              <none>
Selector:                 run=load-balancer-example
Type:                     NodePort
IP:                       10.106.196.12
Port:                     <unset>  8080/TCP
TargetPort:               8080/TCP
NodePort:                 <unset>  31696/TCP
Endpoints:                172.17.1.47:8080,172.17.2.48:8080
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none># 访问
# 通过容器ip:port
[root@server apis]# curl http://172.17.1.47:8080
Hello Kubernetes!
# 通过clusterip:port
[root@server apis]# curl http://10.106.196.12:8080
Hello Kubernetes!
# ping clusterip是不通的
[root@server apis]# ping 10.106.196.12
PING 10.106.196.12 (10.106.196.12) 56(84) bytes of data.
From 10.10.199.130 icmp_seq=1 Time to live exceeded
From 10.10.199.130 icmp_seq=2 Time to live exceeded
^C
--- 10.106.196.12 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1001ms
# 通过集群中的任一物理机加nodePort访问
[root@server apis]# curl http://10.40.2.228:31696
Hello Kubernetes!
[root@server apis]# curl http://10.40.2.229:31696
Hello Kubernetes!
[root@server apis]# curl http://10.40.2.230:31696
Hello Kubernetes!

查看配置文件:

# 查看配置:
[root@server apis]# kubectl edit service/example-service
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: Service
metadata:creationTimestamp: 2018-12-18T10:01:15Zlabels:run: load-balancer-examplename: example-servicenamespace: defaultresourceVersion: "1337064"selfLink: /api/v1/namespaces/default/services/example-serviceuid: db61beec-02ab-11e9-92e8-005056b6756e
spec:clusterIP: 10.106.196.12externalTrafficPolicy: Clusterports:- nodePort: 31696   # 这是宿主机的端口port: 8080        # 我的理解这是cluster ip的端口protocol: TCPtargetPort: 8080  # 这是容器的端口selector:run: load-balancer-examplesessionAffinity: Nonetype: NodePort
status:loadBalancer: {}

注意:其实可以指定某些节点映射nodePort

specexternalIPs:- 10.40.2.230  # 集群中某台宿主机的ip- 10.40.2.228

使用yaml创建配置文件并测试sessionAffinity

# 参考前面的nginx的deployment的yaml配置,这里没有做任何改变
[root@server apis]# kubectl create -f nginx-deployment.yaml[root@server apis]# kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
hello-world-86cddf59d5-tjjh5       1/1       Running   0          16h       172.17.1.47   client02   <none>
hello-world-86cddf59d5-z2q7c       1/1       Running   0          16h       172.17.2.48   client01   <none>
nginx-deployment-764f9dc96-2dtrg   1/1       Running   0          9m        172.17.2.49   client01   <none>
nginx-deployment-764f9dc96-9vqlh   1/1       Running   0          9m        172.17.2.50   client01   <none>
nginx-deployment-764f9dc96-bxsfx   1/1       Running   0          9m        172.17.1.49   client02   <none>
nginx-deployment-764f9dc96-gw9ss   1/1       Running   0          9m        172.17.1.48   client02   <none>
nginx-deployment-764f9dc96-wdsl6   1/1       Running   0          9m        172.17.2.51   client01   <none># 处于运行状态
[root@server apis]# kubectl get rs
NAME                         DESIRED   CURRENT   READY     AGE
hello-world-86cddf59d5       2         2         2         16h
nginx-deployment-764f9dc96   5         5         5         10m# 建立service配置文件
[root@server apis]# cat myservice.yaml
kind: Service
apiVersion: v1
metadata:name: webapp
spec:type: NodePortselector:app: nginxports:- port: 8081targetPort: 80protocol: TCPnodePort: 31697externalIPs:- 10.40.2.230- 10.40.2.228[root@server apis]# kubectl create -f myservice.yaml
service/webapp created
[root@server apis]# kubectl get services
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP               PORT(S)          AGE
example-service   NodePort    10.106.196.12    <none>                    8080:31696/TCP   16h
kubernetes        ClusterIP   10.96.0.1        <none>                    443/TCP          16h
webapp            NodePort    10.100.227.245   10.40.2.230,10.40.2.228   8081:31697/TCP   5s# 验证:
# cluster ip:port
[root@server apis]# curl -I -s http://10.100.227.245:8081 |grep 'HTTP/1.1'|awk '{print $2}'
200
# ★宿主机,service中配置了externalIPs为228和230,但是229也可以访问
[root@server apis]# curl -I -s http://10.40.2.228:31697 |grep 'HTTP/1.1'|awk '{print $2}'
200
[root@server apis]# curl -I -s http://10.40.2.230:31697 |grep 'HTTP/1.1'|awk '{print $2}'
200
[root@server apis]# curl -I -s http://10.40.2.229:31697 |grep 'HTTP/1.1'|awk '{print $2}'
200★这里有一个误解:
externalIPs一般和ClusterIP结合使用,和NodePort没有效果
再看下面的例子:
# 删掉hello kubernetes的service,然后重建一个
[root@server apis]# kubectl delete service example-service
service "example-service" deleted[root@server apis]# cat hello-world-svc.yaml
apiVersion: v1
kind: Service
metadata:name: hello-world-svc
spec:externalIPs:- 10.40.2.228ports:- port: 31696   # ClusterIP的端口,也是externalIPs的端口protocol: TCPtargetPort: 8080selector:run: load-balancer-examplesessionAffinity: Nonetype: ClusterIP[root@server apis]# kubectl create -f hello-world-svc.yaml
service/hello-world-svc created[root@server apis]# curl http://10.103.174.188:31696
Hello Kubernetes!
[root@server apis]# curl http://10.40.2.230:31696
curl: (7) Failed connect to 10.40.2.230:31696; Connection refused注意:这里是ClusterIP和宿主机的端口是一样的,同时是ClusterIP类型
[root@server apis]# kubectl get pods -o wide
NAME                               READY     STATUS    RESTARTS   AGE       IP            NODE       NOMINATED NODE
hello-world-86cddf59d5-tjjh5       1/1       Running   0          16h       172.17.1.47   client02   <none>
hello-world-86cddf59d5-z2q7c       1/1       Running   0          16h       172.17.2.48   client01   <none>
nginx-deployment-764f9dc96-2dtrg   1/1       Running   0          21m       172.17.2.49   client01   <none>
nginx-deployment-764f9dc96-9vqlh   1/1       Running   0          21m       172.17.2.50   client01   <none>
nginx-deployment-764f9dc96-bxsfx   1/1       Running   0          21m       172.17.1.49   client02   <none>
nginx-deployment-764f9dc96-gw9ss   1/1       Running   0          21m       172.17.1.48   client02   <none>
nginx-deployment-764f9dc96-wdsl6   1/1       Running   0          21m       172.17.2.51   client01   <none># 非交互式执行命令,>符号一定要转义
[root@server apis]# kubectl exec nginx-deployment-764f9dc96-2dtrg -c nginx -- /bin/echo -e "pod01\n" \> /usr/share/nginx/html/index.html
pod01> /usr/share/nginx/html/index.html
kubectl exec nginx-deployment-764f9dc96-2dtrg -c nginx -n default -- cat /usr/share/nginx/html/index.html
发现这个定向符好像没有生效,只是将结果打印到了终端,并没有定向到文件中
只有手动到每个pod中执行echo -e "pod01\n" > /usr/share/nginx/html/index.html将每一个nginx的pod中nginx的页面都定义一个标识加以区分开# 测试结果:
[root@server apis]# curl http://10.40.2.230:31697
pod03
[root@server apis]# curl http://10.40.2.230:31697
pod02
[root@server apis]# curl http://10.40.2.230:31697
pod04
[root@server apis]# curl http://10.40.2.230:31697
pod05# 修改sessionAffinity: ClientIP
[root@server apis]# kubectl edit service webapp
service/webapp edited
# 再次测试结果如下
[root@client01 soft]# curl http://10.40.2.230:31697
pod03
[root@client01 soft]# curl http://10.40.2.230:31697
pod03
[root@client01 soft]# curl http://10.40.2.230:31697
pod03

压测看看:

docker的输出filter和format使用方式
扩展:docker命令的输出格式
docker ps -f status=exited --format="{{.Names}}"
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Names}}"
测试kube-proxy的实时内存消耗
安装ab命令
yum -y install httpd-tools
ab --help
-n在测试会话中所执行的请求个数。默认时,仅执行一个请求。请求的总数量
-c一次产生的请求个数。默认是一次一个。请求的用户量
-t测试所进行的最大秒数。其内部隐含值是-n 50000,它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。在不同的宿主机上压测不同的服务:
ab -c 4500 -n 100000 http://10.40.2.229:31696/
ab -c 4500 -n 100000 http://10.40.2.228:31697/watch -n 1 -d "ps -e -o pid,uname,etime,rss,cmd --sort=-rss |egrep -v 'grep|kube-apiserver' |grep -i proxy"
发现集群中所有的内存基本都没有出现变动
对单pod、cluster ip、nodePort进行压测

压测hello kubernetes!的容器

# 对单个pod进行压测
[root@server ~]# kubectl get services
NAME              TYPE        CLUSTER-IP       EXTERNAL-IP               PORT(S)          AGE
example-service   NodePort    10.106.196.12    <none>                    8080:31696/TCP   19h
kubernetes        ClusterIP   10.96.0.1        <none>                    443/TCP          20h
webapp            NodePort    10.100.227.245   10.40.2.230,10.40.2.228   8081:31697/TCP   3h[root@server ~]# ab -c 3500 -n 100000 http://172.17.1.47:8080/
Requests per second:    3360.32 [#/sec] (mean)
......
当把并发上升到4000时,会发生Connection reset by peer这个测试结果不太准,上线之前一定要进行一下专业的测试。
个人每次用ab进行压测,结果都不一样,需要对以下两种方式进行对比
ClusterIP+externalIPs
NodePort

总结:

  • 暂时还没有弄明白kube-proxy后面的详细细节或者说底层原理!!!
  • 在ClusterIP+externalIPs类型时压测发现:对ClusterIP进行压测比对宿主机压测性能稳定一点;在NodePort方式中:对宿主机压测比对ClusterIP进行压测稳定一点。
  • 如果依赖k8s的service做服务间的负载均衡,个人觉得不太好,最好是SOA那种框架,自己做流量的分发,但是对外提供服务可能就要借助其他的方法(在k8s外围弄一个自动化代理平台,nginx集群),像支付这种,调用第三方接口,还要回源,可以使用给固定节点打标签的方式。

(2)service

参考:

  • https://kubernetes.io/docs/concepts/services-networking/service/
  • http://docs.kubernetes.org.cn/703.html

官方的东西太多了,不想去一行一行的翻译,总结了一些关键内容:

  • 基于四层(tcp、udp)
  • service可以为组件之间调用提供一个接口,也可以将程序暴露到k8s集群外部,对外提供服务
  • service借助selector关联后面的deployment的lable,从而达到代理和负载均衡的功能;没有selector就不会自动创建endpoint对象,需要自己手动创建并关联,没有selector的使用场景:
    • 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
    • 希望服务指向另一个 Namespace 中或其它集群中的服务。
    • 正在将工作负载转移到 Kubernetes 集群,和运行在 Kubernetes 集群之外的 backend。
    • ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。 相反地,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。
  • vip和service代理:vip是cluster ip,是一个虚拟ip,ping不通。通过配置iptables来进行路由
    • userspace:用户空间
    • iptables:用户空间,支持基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 “ClientIP” (默认值为 “None”)
    • ipvs:内核空间中的模式,kube-proxy会监视Kubernetes Service对象和Endpoints,调用netlink接口以相应地创建ipvs规则并定期与Kubernetes Service对象和Endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。访问服务时,流量将被重定向到其中一个后端Pod。运行kube-proxy时会检查是否安装了IPVS内核模块,没有安装就退回到iptables模式,支持的负载均衡算法:
      • rr:轮询调度
      • lc:最小连接数
      • dh:目标哈希
      • sh:源哈希
      • sed:最短期望延迟
      • nq: 不排队调度
  • 可以通过spec.clusterIP指定cluster ip,但是必须是api server的参数service-cluster-ip-range CIDR 范围内
  • Headless Service:通过spec.clusterIP 为None设置,有不需要或不想要负载均衡,以及单独的 Service IP
    • Selector
    • 无Selector
  • 发布服务:
    • ClusterIP:过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
    • NodePort:通过每个Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 <NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。原理可以理解是每个node上的kube-proxy都会维护每个服务的路由表;注意这个一旦配置了,就所有节点都对外了。
    • LoadBalancer:第三方云平台的东西
    • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容
  • 附:
    • 创建endpoint对象的yaml文件

      kind: Endpoints
      apiVersion: v1
      metadata:name: my-service
      subsets:- addresses:- ip: 1.2.3.4ports:- port: 9376
      # 请求将被路由到用户定义的 Endpoint(该示例中为 1.2.3.4:9376)。
      # Endpoint IP 地址不能是 loopback(127.0.0.0/8)、 link-local(169.254.0.0/16)、或者 link-local 多播(224.0.0.0/24)。
      
    • externalName:
      kind: Service
      apiVersion: v1
      metadata:name: my-servicenamespace: prod
      spec:type: ExternalNameexternalName: my.database.example.com
      
    • 多端口service:
      kind: Service
      apiVersion: v1
      metadata:name: my-service
      spec:selector:app: MyAppports:- name: httpprotocol: TCPport: 80targetPort: 9376- name: httpsprotocol: TCPport: 443targetPort: 9377
      

(3)Ingress

# 将deployment的副本改成1,一会之后立即又扩容到5个节点
[root@server apis]# kubectl scale deployment nginx-deployment --replicas=1
deployment.extensions/nginx-deployment scaled[root@server apis]# kubectl get hpa
NAME               REFERENCE                     TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
nginx-deployment   Deployment/nginx-deployment   <unknown>/80%   5         7         1          4d
[root@server apis]# kubectl delete hpa nginx-deployment
horizontalpodautoscaler.autoscaling "nginx-deployment" deleted

a、简介

  • 可以给Ingress配置提供外部可访问的URL、负载均衡、SSL、基于名称的虚拟主机等。用户通过POST Ingress资源到API server的方式来请求ingress。 Ingress controller负责实现Ingress,通常使用负载平衡器,它还可以配置边界路由和其他前端,这有助于以HA方式处理流量。
  • 单纯的创建一个Ingress没有任何意义,需要一个Ingress Controller来实现Ingress。
  • ingress其实是在k8s集群与外部系统之间的纽带,连接外部系统与k8s集群
internet|
[ Ingress ]
--|-----|--
[ Services ]

b、ingress controller

    要想ingress资源能生效,集群必须有一个ingress控制器在运行。这个不像其他类型的控制器,其他类型的控制器一般会作为二进制kube-controller-manager的一部分随集群一起启动。选择一个最适合你集群的ingress控制器:

  • kubernetes当前将GCE和nginx控制器作为一个项目来支持和维护
  • 其他的控制器:
    • Contour是被Heptio所支持的一个基于Envoy的ingress控制器
    • F5…
    • Haproxy…
    • lstio 基于ingress空气之控制ingress流量
    • KONG 提供社区和商业的支持和维护
    • NGINX Inc…
    • Traefik 是一个功能特别齐全的ingress控制器(支持Let’s Encrypt, secrets, http2, websocket),并且它附带Containous的社区支持

    你可以在一个集群内部部署多个ingress控制器,当你创建了一个ingress(对象),如果你的集群中不止有一个ingress控制器,应该注解(通过注解绑定)每个ingress对象到合适的ingress-class从而宣告使用哪个控制器。如果你没有定义一个类(ingress-class),你的云提供商可以用一个默认的ingress提供者。

注意:
不同的ingress控制器的操作可能有轻微的不同

c、The Ingress Resource

一个最小的ingress例子:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: test-ingressannotations:nginx.ingress.kubernetes.io/rewrite-target: /
spec:rules:- http:paths:- path: /testpathbackend:serviceName: testservicePort: 80
  • 像其他所有的k8s资源对象,ingress需要apiVersion, kind, 和 metadata字段
  • ingress往往基于ingress控制器使用annotations去配置一些选项,一个例子是对目标url进行重写,不同的ingress控制器支持不同的annotations,去对应的文档查看
  • ingress spec有所有需要配置负载均衡或者代理服务器的配置信息,最重要的是包含了一系列的匹配进入的请求流量,仅仅支持HTTP流量的规则

ingress规则:

  • 可选的host
  • paths的列表
  • 后端绑定的 service和端口 名字
  • 一个默认的后端常常配置在ingress控制器,当service的所有请求都没有匹配到列举的规则时路由到默认后端。

d、Types of Ingress

下面的内容不去一一翻译了,可以参考官网

single sevice ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: test-ingress
spec:backend:serviceName: testsvcservicePort: 80
simple fanout

A fanout configuration routes traffic from a single IP address to more than one service,

apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: simple-fanout-exampleannotations:nginx.ingress.kubernetes.io/rewrite-target: /
spec:rules:- host: foo.bar.comhttp:paths:- path: /foobackend:serviceName: service1servicePort: 4200- path: /barbackend:serviceName: service2servicePort: 8080
Name based virtual hosting
foo.bar.com --|                 |-> foo.bar.com s1:80| 178.91.123.132  |
bar.foo.com --|                 |-> bar.foo.com s2:80
apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: name-virtual-host-ingress
spec:rules:- host: foo.bar.comhttp:paths:- backend:serviceName: service1servicePort: 80- host: bar.foo.comhttp:paths:- backend:serviceName: service2servicePort: 80

添加默认后端:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: name-virtual-host-ingress
spec:rules:- host: first.bar.comhttp:paths:- backend:serviceName: service1servicePort: 80- host: second.foo.comhttp:paths:- backend:serviceName: service2servicePort: 80- http:paths:- backend:serviceName: service3servicePort: 80
TLS

创建secret参考:https://kubernetes.io/docs/concepts/configuration/secret/

apiVersion: v1
data:tls.crt: base64 encoded certtls.key: base64 encoded key
kind: Secret
metadata:name: testsecret-tlsnamespace: default
type: Opaque
apiVersion: extensions/v1beta1
kind: Ingress
metadata:name: tls-example-ingress
spec:tls:- hosts:- sslexample.foo.comsecretName: testsecret-tlsrules:- host: sslexample.foo.comhttp:paths:- path: /backend:serviceName: service1servicePort: 80

e、Updating an Ingress

方式:

  • kubectl edit ingress test
  • kubectl replace -f test-new.yaml(这个文件是新的)
  • 应该可以用kubectl patch …

f、对于边缘节点也就是ingress controller一点个人看法

  • 所谓的边缘节点,就是集群的node节点,这些节点可以不部署业务docker,直接在宿主机上面跑ingress控制器,或者将边缘控制器放在pod里面。放在pod里面,也就纳入了k8s集群的管理范围内,这样我们可以方便的使用ingress来进行流量透传的管理。鱼和熊掌的选择来了,这样是便利了,但是性能肯定是有影响,而且业务专有的配置和特性可能无法实现,如果不是要弄容器云这种,生产上建议使用nginx+lua[+init]容器来进行流量透传,这样可以直接透传cluster ip或者直接透传pod的流量
  • 不过很看好Istio未来的发展
    • 官网:https://istio.io/
    • 代码:https://github.com/istio/istio
  • Envoy(一个高性能轻量级代理)

(4)Adding entries to Pod /etc/hosts with HostAliases

参考:https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/

......
spechostAliases- ip: 127.0.0.1hostnames:- "foo.local"- "bar.local"
......
[root@server apis]# kubectl exec hello-world-86cddf59d5-w7kcr -- cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
172.17.1.54 hello-world-86cddf59d5-w7kcr

尝试用path去更新时报错:

[root@server apis]# kubectl patch pod hello-world-86cddf59d5-w7kcr -p '{"spec": {"hostAliases": [{"ip": "127.0.0.1","hostnames": ["foo.local"]}]}}'
The Pod "hello-world-86cddf59d5-w7kcr" is invalid: spec: Forbidden: pod updates may not change fields other than `spec.containers[*].image`, `spec.initContainers[*].image`, `spec.activeDeadlineSeconds` or `spec.tolerations` (only additions to existing tolerations)
......

(5)Network Policy

7、存储

(1)secret

a、Opaque

  • paque类型的数据是一个map类型,要求value是base64编码格式:

    $ echo -n "admin" | base64
    YWRtaW4=
    $ echo -n "1f2d1e2e67df" | base64
    MWYyZDFlMmU2N2Rm
    
  • secrets.yml
    apiVersion: v1
    kind: Secret
    metadata:name: mysecret
    type: Opaque
    data:password: MWYyZDFlMmU2N2Rmusername: YWRtaW4=
    
  • 创建secret:kubectl create -f secrets.yml
  • 两种使用方式:
    • 将Secret挂载到Volume中

      apiVersion: v1
      kind: Pod
      metadata:labels:name: dbname: db
      spec:volumes:- name: secretssecret:secretName: mysecretcontainers:- image: gcr.io/my_project_id/pg:v1name: dbvolumeMounts:- name: secretsmountPath: "/etc/secrets"readOnly: trueports:- name: cpcontainerPort: 5432hostPort: 5432
      
    • 将Secret导出到环境变量中
      apiVersion: extensions/v1beta1
      kind: Deployment
      metadata:name: wordpress-deployment
      spec:replicas: 2strategy:type: RollingUpdatetemplate:metadata:labels:app: wordpressvisualize: "true"spec:containers:- name: "wordpress"image: "wordpress"ports:- containerPort: 80env:- name: WORDPRESS_DB_USERvalueFrom:secretKeyRef:name: mysecretkey: username- name: WORDPRESS_DB_PASSWORDvalueFrom:secretKeyRef:name: mysecretkey: password
      

b、kubernetes.io/dockerconfigjson

  • 可以直接用kubectl命令来创建用于docker registry认证的secret:

    $ kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
    secret "myregistrykey" created.
    
  • 也可以直接读取~/.docker/config.json的内容来创建:
    $ cat ~/.docker/config.json | base64
    $ cat > myregistrykey.yaml <<EOF
    apiVersion: v1
    kind: Secret
    metadata:name: myregistrykey
    data:.dockerconfigjson: UmVhbGx5IHJlYWxseSByZWVlZWVlZWVlZWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx5eXl5eXl5eXl5eXl5eXl5eXl5eSBsbGxsbGxsbGxsbGxsbG9vb29vb29vb29vb29vb29vb29vb29vb29vb25ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2cgYXV0aCBrZXlzCg==
    type: kubernetes.io/dockerconfigjson
    EOF
    $ kubectl create -f myregistrykey.yaml
    
  • 在创建Pod的时候,通过imagePullSecrets来引用刚创建的myregistrykey:
    apiVersion: v1
    kind: Pod
    metadata:name: foo
    spec:containers:- name: fooimage: janedoe/awesomeapp:v1imagePullSecrets:- name: myregistrykey
    

c、Service Account

  • Service Account用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中。

    $ kubectl run nginx --image nginx
    deployment "nginx" created
    $ kubectl get pods
    NAME                     READY     STATUS    RESTARTS   AGE
    nginx-3137573019-md1u2   1/1       Running   0          13s
    $ kubectl exec nginx-3137573019-md1u2 ls /run/secrets/kubernetes.io/serviceaccount
    ca.crt
    namespace
    token
    

(2)configmap

  • 照抄自:

    • https://jimmysong.io/kubernetes-handbook/concepts/configmap.html
  • ConfigMap API给我们提供了向容器中注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象。
  • 概览:
kind: ConfigMap
apiVersion: v1
metadata:creationTimestamp: 2016-02-18T19:14:38Zname: example-confignamespace: default
data:example.property.1: helloexample.property.2: worldexample.property.file: |-property.1=value-1property.2=value-2property.3=value-3
  • 用法:

    • 设置环境变量的值
    • 在容器里设置命令行参数
    • 在数据卷里面创建config文件

a、创建configmap

从目录创建
$ ls docs/user-guide/configmap/kubectl/
game.properties
ui.properties$ cat docs/user-guide/configmap/kubectl/game.properties
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30$ cat docs/user-guide/configmap/kubectl/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice$ kubectl create configmap game-config --from-file=docs/user-guide/configmap/kubectl$ kubectl describe configmaps game-config
Name:           game-config
Namespace:      default
Labels:         <none>
Annotations:    <none>Data
====
game.properties:        158 bytes
ui.properties:          83 bytes# 我们以yaml格式输出配置。
$ kubectl get configmaps game-config -o yaml
apiVersion: v1
data:game.properties: |enemies=alienslives=3enemies.cheat=trueenemies.cheat.level=noGoodRottensecret.code.passphrase=UUDDLRLRBABASsecret.code.allowed=truesecret.code.lives=30ui.properties: |color.good=purplecolor.bad=yellowallow.textmode=truehow.nice.to.look=fairlyNice
kind: ConfigMap
metadata:creationTimestamp: 2016-02-18T18:34:05Zname: game-confignamespace: defaultresourceVersion: "407"selfLink: /api/v1/namespaces/default/configmaps/game-configuid: 30944725-d66e-11e5-8cd0-68f728db1985
从文件创建

和上面从目录创建一样一样的,只是–from-file参数指定一个文件就行了

从字面量创建
$ kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm$ kubectl get configmaps special-config -o yaml
apiVersion: v1
data:special.how: veryspecial.type: charm
kind: ConfigMap
metadata:creationTimestamp: 2016-02-18T19:14:38Zname: special-confignamespace: defaultresourceVersion: "651"selfLink: /api/v1/namespaces/default/configmaps/special-configuid: dadce046-d673-11e5-8cd0-68f728db1985

b、pod中使用configmap

代替环境变量
apiVersion: v1
kind: ConfigMap
metadata:name: special-confignamespace: default
data:special.how: veryspecial.type: charmapiVersion: v1
kind: ConfigMap
metadata:name: env-confignamespace: default
data:log_level: INFO# 使用
apiVersion: v1
kind: Pod
metadata:name: dapi-test-pod
spec:containers:- name: test-containerimage: gcr.io/google_containers/busyboxcommand: [ "/bin/sh", "-c", "env" ]env:- name: SPECIAL_LEVEL_KEYvalueFrom:configMapKeyRef:name: special-configkey: special.how- name: SPECIAL_TYPE_KEYvalueFrom:configMapKeyRef:name: special-configkey: special.typeenvFrom:- configMapRef:name: env-configrestartPolicy: Never
用ConfigMap设置命令行参数
apiVersion: v1
kind: ConfigMap
metadata:name: special-confignamespace: default
data:special.how: veryspecial.type: charm# 使用
apiVersion: v1
kind: Pod
metadata:name: dapi-test-pod
spec:containers:- name: test-containerimage: gcr.io/google_containers/busyboxcommand: [ "/bin/sh", "-c", "echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]env:- name: SPECIAL_LEVEL_KEYvalueFrom:configMapKeyRef:name: special-configkey: special.how- name: SPECIAL_TYPE_KEYvalueFrom:configMapKeyRef:name: special-configkey: special.typerestartPolicy: Never
通过数据卷插件使用ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:name: special-confignamespace: default
data:special.how: veryspecial.type: charm# 使用1
apiVersion: v1
kind: Pod
metadata:name: dapi-test-pod
spec:containers:- name: test-containerimage: gcr.io/google_containers/busyboxcommand: [ "/bin/sh", "-c", "cat /etc/config/special.how" ]volumeMounts:- name: config-volumemountPath: /etc/configvolumes:- name: config-volumeconfigMap:name: special-configrestartPolicy: Never# 使用2——可以在ConfigMap值被映射的数据卷里控制路径
apiVersion: v1
kind: Pod
metadata:name: dapi-test-pod
spec:containers:- name: test-containerimage: gcr.io/google_containers/busyboxcommand: [ "/bin/sh","-c","cat /etc/config/path/to/special-key" ]volumeMounts:- name: config-volumemountPath: /etc/configvolumes:- name: config-volumeconfigMap:name: special-configitems:- key: special.howpath: path/to/special-keyrestartPolicy: Never

c、注意:

参考: https://jimmysong.io/kubernetes-handbook/concepts/configmap-hot-update.html

  • 通过configmap设置环境变量,无法热更新
  • 通过configmap volume,过10s左右会更新

(3)volume

a、部分volume类型

cephfs
  • cephfs 卷允许将现有的 CephFS 卷挂载到您的容器中。与 emptyDir 类型会在删除 Pod 时被清除不同,Cephfs 卷的的内容会保留下来,仅仅是被卸载。这意味着 CephFS 卷可以预先填充数据,并且可以在数据包之间“切换”数据。 CephFS 可以被多个写设备同时挂载。
  • 重要提示:您必须先拥有自己的 Ceph 服务器,然后才能使用它。
  • 实例:
    • 参考:https://github.com/kubernetes/examples/tree/master/staging/volumes/cephfs/
configMap
  • 单独出来了
emptyDir
  • 当 Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写入 emptyDir 卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时,emptyDir 中的数据将被永久删除。
  • 注意:容器崩溃不会从节点中移除 pod,因此 emptyDir 卷中的数据在容器崩溃时是安全的。
  • emptyDir 的用法有:
    • 暂存空间,例如用于基于磁盘的合并排序、nginx缓存等等
    • 用作长时间计算崩溃恢复时的检查点
    • Web服务器容器提供数据时,保存内容管理器容器提取的文件
glusterfs
  • glusterfs 卷允许将 Glusterfs(一个开放源代码的网络文件系统)卷挂载到您的集群中。与删除 Pod 时删除的 emptyDir 不同,glusterfs 卷的内容将被保留,而卷仅仅被卸载。这意味着 glusterfs 卷可以预先填充数据,并且可以在数据包之间“切换”数据。 GlusterFS 可以同时由多个写入挂载。
  • 重要提示:您必须先自行安装 GlusterFS,才能使用它。
  • 实例:
    • 参考:https://github.com/kubernetes/examples/tree/master/staging/volumes/glusterfs
Downward API
  • 个人认为日志收集中很有用
  • 参考:
    • https://v1-11.docs.kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/
  • 有两种方法可以暴露 Pod 和 Container 字段给一个运行的容器:
    • 环境变量

      • 参考:https://v1-11.docs.kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/
    • DownwardAPIVolumeFiles
  • 例1:
    apiVersion: v1
    kind: Pod
    metadata:name: kubernetes-downwardapi-volume-examplelabels:zone: us-est-coastcluster: test-cluster1rack: rack-22annotations:build: twobuilder: john-doe
    spec:containers:- name: client-containerimage: k8s.gcr.io/busyboxcommand: ["sh", "-c"]args:- while true; doif [[ -e /etc/podinfo/labels ]]; thenecho -en '\n\n'; cat /etc/podinfo/labels; fi;if [[ -e /etc/podinfo/annotations ]]; thenecho -en '\n\n'; cat /etc/podinfo/annotations; fi;sleep 5;done;volumeMounts:- name: podinfomountPath: /etc/podinforeadOnly: falsevolumes:- name: podinfodownwardAPI:items:- path: "labels"fieldRef:fieldPath: metadata.labels- path: "annotations"fieldRef:fieldPath: metadata.annotations
    
  • 例2:
    apiVersion: v1
    kind: Pod
    metadata:name: kubernetes-downwardapi-volume-example-2
    spec:containers:- name: client-containerimage: k8s.gcr.io/busybox:1.24command: ["sh", "-c"]args:- while true; doecho -en '\n';if [[ -e /etc/podinfo/cpu_limit ]]; thenecho -en '\n'; cat /etc/podinfo/cpu_limit; fi;if [[ -e /etc/podinfo/cpu_request ]]; thenecho -en '\n'; cat /etc/podinfo/cpu_request; fi;if [[ -e /etc/podinfo/mem_limit ]]; thenecho -en '\n'; cat /etc/podinfo/mem_limit; fi;if [[ -e /etc/podinfo/mem_request ]]; thenecho -en '\n'; cat /etc/podinfo/mem_request; fi;sleep 5;done;resources:requests:memory: "32Mi"cpu: "125m"limits:memory: "64Mi"cpu: "250m"volumeMounts:- name: podinfomountPath: /etc/podinforeadOnly: falsevolumes:- name: podinfodownwardAPI:items:- path: "cpu_limit"resourceFieldRef:containerName: client-containerresource: limits.cpu- path: "cpu_request"resourceFieldRef:containerName: client-containerresource: requests.cpu- path: "mem_limit"resourceFieldRef:containerName: client-containerresource: limits.memory- path: "mem_request"resourceFieldRef:containerName: client-containerresource: requests.memory
    
  • Downward API 能将一下信息提供给容器:
    • Node 名称
    • Node IP
    • Pod 名称
    • Pod namespace
    • Pod IP 地址
    • Pod 的 serviceaccount 名称
    • Pod 的 UID
    • 容器的 CPU limit
    • 容器的 CPU request
    • 容器的内存 limit
    • 容器的内存 request
    • 另外,通过 DownwardAPIVolumeFiles 还可以提供以下信息:
      • Pod 的 labels
      • Pod 的 annotations
hostPath
  • hostPath 卷将主机节点的文件系统中的文件或目录挂载到集群中。该功能大多数 Pod 都用不到,但它为某些应用程序提供了一个强大的解决方法。

  • 常见用途:

    • 运行需要访问 Docker 内部的容器;使用 /var/lib/docker 的 hostPath
    • 在容器中运行 cAdvisor;使用 /dev/cgroups 的 hostPath
    • 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建,以及它应该以什么形式存在
  • 除了所需的 path 属性之外,用户还可以为 hostPath 卷指定 type:

    行为
    空字符串 空字符串(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。
    DirectoryOrCreate 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。
    Directory 给定的路径下必须存在目录
    FileOrCreate 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。
    File 给定的路径下必须存在文件
    Socket 给定的路径下必须存在 UNIX 套接字
    CharDevice 给定的路径下必须存在字符设备
    BlockDevice 给定的路径下必须存在块设备
  • 注意:

    • 由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的 pod 在不同节点上的行为可能会有所不同
    • 当 Kubernetes 按照计划添加资源感知调度时,将无法考虑 hostPath 使用的资源
    • 在底层主机上创建的文件或目录只能由 root 写入。您需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入 hostPath 卷
  • 案例:

    • 当时设想:使用hostPath来存储pod中业务容器的日志,然后通过宿主机的单独网络,使用filebeat发送到kafka,这样日志收集流量、监控流量和业务流量可以分开。但是宿主机在接入k8s资源池时就要标准化、规划好目录,包括权限,另外pod或者pod内容器中要设置Security Context

      spec:securityContext:runAsUser: 1000fsGroup: 2000volumes:- name: sec-ctx-volemptyDir: {}containers:- name: sec-ctx-demoimage: gcr.io/google-samples/node-hello:1.0volumeMounts:- name: sec-ctx-volmountPath: /data/demosecurityContext:allowPrivilegeEscalation: false # 不建议设置为True,如果为True,里面的程序将以特权模式root运行
      
local
  • 要求启用 PersistentLocalVolumes feature gate,从 1.9 开始,VolumeScheduling feature gate 也必须启用。
  • local 卷表示挂载的本地存储设备,如磁盘、分区或目录。本地卷只能用作静态创建的PersistentVolume。
  • 与 HostPath 卷相比,local 卷可以以持久的方式使用,而无需手动将 pod 调度到节点上,因为系统会通过查看 PersistentVolume 上的节点关联性来了解卷的节点约束。但是,local 卷仍然受底层节点的可用性影响,并不适用于所有应用程序。
  • 下面是PersistentVolume用local volume and nodeAffinity的例子:
    apiVersion: v1
    kind: PersistentVolume
    metadata:name: example-pv
    spec:capacity:storage: 100Gi# volumeMode field requires BlockVolume Alpha feature gate to be enabled.volumeMode: FilesystemaccessModes:- ReadWriteOncepersistentVolumeReclaimPolicy: DeletestorageClassName: local-storagelocal:path: /mnt/disks/ssd1nodeAffinity:required:nodeSelectorTerms:- matchExpressions:- key: kubernetes.io/hostnameoperator: Invalues:- example-node
    
  • 注意:
    • 本地 PersistentVolume 清理和删除需要手动干预,当无外部提供程序。
    • PersistentVolume nodeAffinity is required when using local volumes.
    • 详情参考:https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume
nfs
  • nfs 卷允许将现有的 NFS(网络文件系统)共享挂载到您的容器中。不像 emptyDir,当删除 Pod 时,nfs 卷的内容被保留,卷仅仅是被卸载。这意味着 NFS 卷可以预填充数据,并且可以在 pod 之间“切换”数据。 NFS 可以被多个写入者同时挂载。
  • 重要提示:您必须先拥有自己的 NFS 服务器才能使用它,然后才能使用它。
  • 实例参考:https://github.com/kubernetes/examples/tree/master/staging/volumes/nfs
persistentVolumeClaim
  • persistentVolumeClaim 卷用于将 PersistentVolume 挂载到容器中。PersistentVolumes 是在用户不知道特定云环境的细节的情况下“声明”持久化存储(例如 GCE PersistentDisk 或 iSCSI 卷)的一种方式。
  • 实例参考:https://v1-11.docs.kubernetes.io/docs/concepts/storage/persistent-volumes/
projected
  • projected 卷将几个现有的卷源映射到同一个目录中。目前,可以映射以下类型的卷来源:

    • secret
    • downwardAPI
    • configMap
    • serviceAccountToken # 老版本不支持
  • 所有来源都必须在与 pod 相同的命名空间中
    apiVersion: v1
    kind: Pod
    metadata:name: volume-test
    spec:containers:- name: container-testimage: busyboxvolumeMounts:- name: all-in-onemountPath: "/projected-volume"readOnly: truevolumes:- name: all-in-oneprojected:sources:- secret:name: mysecretitems:- key: usernamepath: my-group/my-username- secret:name: mysecret2items:- key: passwordpath: my-group/my-passwordmode: 511
    
secret
  • secret 卷用于将敏感信息(如密码)传递到 pod。您可以将 secret 存储在 Kubernetes API 中,并将它们挂载为文件,以供 Pod 使用,而无需直接连接到 Kubernetes。 secret 卷由 tmpfs(一个 RAM 支持的文件系统)支持,所以它们永远不会写入非易失性存储器。

b、使用子路径

  • 有时,在单个容器中共享一个卷用于多个用途是有用的。volumeMounts.subPath 属性可用于在引用的卷内而不是其根目录中指定子路径。
  • 下面是一个使用单个共享卷的 LAMP 堆栈(Linux Apache Mysql PHP)的示例。 HTML 内容被映射到它的 html 目录,数据库将被存储在它的 mysql 目录中:
    apiVersion: v1
    kind: Pod
    metadata:name: my-lamp-site
    spec:containers:- name: mysqlimage: mysqlenv:- name: MYSQL_ROOT_PASSWORDvalue: "rootpasswd"volumeMounts:- mountPath: /var/lib/mysqlname: site-datasubPath: mysql- name: phpimage: php:7.0-apachevolumeMounts:- mountPath: /var/www/htmlname: site-datasubPath: htmlvolumes:- name: site-datapersistentVolumeClaim:claimName: my-lamp-site-data
    
  • 使用子路径扩展环境变量(1.11是alpha版本)
    • Before you use this feature, you must enable the VolumeSubpathEnvExpansionfeature gate.
    • 下面的例子,一个pod使用子路径创建pod1目录,hostPath volume是/var/log/pods,从Downward API使用pod名字,宿主机上的路径/var/log/pods/pod1被挂在到容器里面的/logs
      apiVersion: v1
      kind: Pod
      metadata:name: pod1
      spec:containers:- name: container1env:- name: POD_NAMEvalueFrom:fieldRef:apiVersion: v1fieldPath: metadata.nameimage: busyboxcommand: [ "sh", "-c", "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ]volumeMounts:- name: workdir1mountPath: /logssubPath: $(POD_NAME)restartPolicy: Nevervolumes:- name: workdir1hostPath:path: /data/log/pods
      # 1.11.5实验过程中,子路径是$(POS_NAME),并没有换成对应的值,不知道是不是上面的feature是不是没有开启
      

c、资源

  • mptyDir 卷的存储介质(磁盘、SSD 等)由保存在 kubelet 根目录的文件系统的介质(通常是 /var/lib/kubelet)决定。 emptyDir 或 hostPath 卷可占用多少空间并没有限制,容器之间或 Pod 之间也没有隔离。
  • 在将来,我们预计 emptyDir 和 hostPath 卷将能够使用 resource 规范请求一定的空间,并选择要使用的介质,适用于具有多种媒体类型的集群

(4)persistent volume

参考:https://v1-11.docs.kubernetes.io/docs/concepts/storage/persistent-volumes/

  • PersistentVolume(PV)是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是 Volume 之类的卷插件,但具有独立于使用 PV 的 Pod 的生命周期。此 API 对象包含存储实现的细节,即 NFS、iSCSI 或特定于云供应商的存储系统。
  • PersistentVolumeClaim(PVC)是用户存储的请求。它与 Pod 相似。Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。声明可以请求特定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)。
  • 这篇看别人翻译的和官方文档,都晕头转向,最后从下面看,然后回过头来看前面的,所以这里和官方文档的顺序不太一样

a、PV

  • 每个PV都有一个spec和status

    apiVersion: v1
    kind: PersistentVolume
    metadata:name: pv0003
    spec:capacity:storage: 5GivolumeMode: FilesystemaccessModes:- ReadWriteOncepersistentVolumeReclaimPolicy: RecyclestorageClassName: slowmountOptions:- hard- nfsvers=4.1nfs:path: /tmpserver: 172.17.0.2
    
  • 容量:一般一个PV都会指定一个存储容量,目前只支持容量,不支持IOPS等等
  • volume的模式
    • 开启此功能需要在apiServer、controller-manager 和 the kubelet开启BlockVolume特性
    • 1.9版本之前,所有的都是filesystem模式,现在还支持raw模式
  • 访问模式
    • 各个PV类型支持的访问模式不太一样,参考PV官网
    • ReadWriteOnce – 只能被一个node使用
    • ReadOnlyMany – 能被多个node只读方式挂载
    • ReadWriteMany – 可以被多个node读写方式挂载
    • 命令行方式
      • RWO - ReadWriteOnce
      • ROX - ReadOnlyMany
      • RWX - ReadWriteMany
  • class
    • 一个PV有一个class,它通过设置storageClassName属性被用来指定一个StorageClass的名字,一个专属的PV class仅仅能绑定到对应的PVCs,没有storageClassName的PV仅仅能绑定到没有特殊class的PVCs。以前是用annotation,后期会丢弃。
  • 回收策略
    • retain —— 人为回收
    • recycle —— basice scrub (rm -rf /thevolume/*)
    • delete —— 部分支持
    • 注意:当前仅仅nfs和hostPath支持recycle,而AWS EBS, GCE PD, Azure Disk, and Cinder 卷 支持删除
  • 挂载参数
    • 老版本是用annotation,现在是用的mountOptions设置挂载参数
    • 仅仅部分支持挂载参数:NFS、iSCSI、RBD、CephFS、Cinder、Glusterfs、vsphereVolume
  • Phase(可以理解为状态)
    • Available – 空闲资源,没有和claim绑定
    • Bound – 和claim绑定的volume
    • Released – 和他绑定的claim已经删除了,但是资源在集群中还没有回收
    • Failed – 动态和claim绑定失败

b、PV的种类

  • NFS
  • iSCSI
  • RBD (Ceph Block Device)
  • CephFS
  • Cinder (OpenStack block storage)
  • Glusterfs
  • VsphereVolume
  • HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)

c、PVC

  • 每个PVC有一个spec和status

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:name: myclaim
    spec:accessModes:- ReadWriteOncevolumeMode: Filesystemresources:requests:storage: 8GistorageClassName: slowselector:matchLabels:release: "stable"matchExpressions:- {key: environment, operator: In, values: [dev]}
    
  • 访问模式:和PV一样的
  • volume模式:和PV一样的
  • 资源:像pods,可以请求指定量的资源,不过这里指的是存储,pv和pvc的资源要一样哦
  • selector:claim可以指定一个标签选择器进而过滤volume,仅仅匹配选择器的volume能和claim绑定,选择器可以包含两个字段
    • matchLabels:volume必须用户标签选择器的值的标签
    • matchExpressions:一个标签匹配表达式,可以用In、NotIn、Exists或者DoesNotExist
    • 上面两者是and的关系,就是当两者都存在时必须都匹配才行
  • class
    • 一个PV有一个class,它通过设置storageClassName属性被用来指定一个StorageClass的名字,一个专属的PV class仅仅能绑定到对应的PVCs,没有storageClassName的PV仅仅能绑定到没有特殊class的PVCs
    • 一个PVC没有storageClassName,和DefaultStorageClass准入控制插件是否开启有关:
      • 打开:简单说就是设置storageClassName了的PV和PVC绑定,没有设置storageClassName的PVC将自动绑定默认的StorageClass,默认的StorageClass由管理员在StorageClass的定义中通过storageclass.kubernetes.io/is-default-class equal to “true” in a StorageClass object. 来设置
      • 关闭: All PVCs that have no storageClassName can be bound only to PVs that have no class.

d、Claims As Volumes

  • pods访问存储使用claim当一个volume,claim必须和pod在同一个命名空间,集群在pod的命名空间中找到claim,同时使用它找到PV,此PV volume将被挂载到此主机进而到pod

    kind: Pod
    apiVersion: v1
    metadata:name: mypod
    spec:containers:- name: myfrontendimage: dockerfile/nginxvolumeMounts:- mountPath: "/var/www/html"name: mypdvolumes:- name: mypdpersistentVolumeClaim:claimName: myclaim
    
  • 命名空间的注意点
    • PV的绑定是排外的的,同时因为PVC是命名空间里的对象,以“Many”模式(ROX,RWX)挂在claim时仅仅可以在同一个命名空间内
  • Writing Portable(轻便的,有人翻译为可移植的) Configuration:不去翻译,总结如下两点:
    • PV、PVC都单独写,不要在deployment等配置中组合PV、PVC的定义
    • 尽量指定stroageClass

e、raw block volume的支持(1.9是alpha)

  • 支持:

    • iSCSI
    • Local volume
    • RBD(ceph block device)
  • 详细实例参考官网

f、卷和声明的生命周期

  • 配置(Provision)

    • 静态

      • 集群管理员创建一些 PV。它们带有可供群集用户使用的实际存储的细节。它们存在于 Kubernetes API 中,可用于消费。
    • 动态
      • 当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,集群可能会尝试动态地为 PVC 创建卷。此配置基于 StorageClasses:PVC 必须请求存储类,并且管理员必须创建并配置该类才能进行动态创建。声明该类为 “” 可以有效地禁用其动态配置。
      • 要启用基于存储级别的动态存储配置,集群管理员需要启用 API server 上的 DefaultStorageClass 准入控制器。例如,通过确保 DefaultStorageClass 位于 API server 组件的 --enable-admission-plugins标志,使用逗号分隔的有序值列表中,可以完成此操作。
  • 绑定
    • PVC 跟 PV 绑定是一对一的映射。
    • 用户创建或已经创建了具有特定存储量的 PersistentVolumeClaim 以及某些访问模式。master 中的控制环路监视新的 PVC,寻找匹配的 PV(如果可能),并将它们绑定在一起。如果为新的 PVC 动态调配 PV,则该环路将始终将该 PV 绑定到 PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦 PV 和 PVC 绑定后,PersistentVolumeClaim 绑定是排他性的,不管它们是如何绑定的。 PVC 跟 PV 绑定是一对一的映射。
  • 持久化卷声明的保护:
    • 当启用PVC 保护 alpha 功能时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除。PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用。
    • 参考:https://v1-11.docs.kubernetes.io/docs/tasks/administer-cluster/storage-object-in-use-protection/
  • 回收:用户用完 volume 后,可以从允许回收资源的 API 中删除 PVC 对象。PersistentVolume 的回收策略告诉集群在PVC释放后应如何处理该卷(PV)。目前,volume 的处理策略有保留、回收或删除,注意,下面的三种策略,不是每种PV都支持的
    • 保留:当PVC被删除,PV仍然存在,并且被人为是“released”状态,但是它对其他的PVC是不能用的,因为前一个PVC的数据仍然存在这个volume上,管理员可以手动回收这个volume,按如下步骤:

      • 删除pv,删除PV之后与之关联的外部存储资产(例如:Cinder volume)仍然存在
      • 手动清除外部存储与之对应的数据
      • 如果还想重用同样的存储资产,可以根据前面PV的定义创建一个新的PV
    • 删除:同时删除pv和pvc,包括pv对应的存储资产;volume会动态继承StorageClass的默认的回收策略;注意只有部分支持哦。
    • 重复利用(recycle):被丢弃了,建议使用动态提供;
      • 当底层volume插件支持时,会在volume上执行基本的数据擦洗(rm -rf /thevolume/*)以便claim可以再次使用
      • 当然,管理员可以配置一个定制的回收器模板(pod),必须包含一个volume,如下:
        apiVersion: v1
        kind: Pod
        metadata:name: pv-recyclernamespace: default
        spec:restartPolicy: Nevervolumes:- name: volhostPath:path: /any/path/it/will/be/replacedcontainers:- name: pv-recyclerimage: "k8s.gcr.io/busybox"command: ["/bin/sh", "-c", "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/*  && test -z \"$(ls -A /scrub)\" || exit 1"]volumeMounts:- name: volmountPath: /scrub
        
  • 扩展持久存储声明(PVC)
    • FEATURE STATE: Kubernetes v1.8 alpha
    • FEATURE STATE: Kubernetes v1.11 beta
    • 仅仅如下volume支持(仅仅列出常用的部分):
      • Cinder
      • glusterfs
      • rbd
    • StorageClass中必须开启:
      kind: StorageClass
      apiVersion: storage.k8s.io/v1
      metadata:name: gluster-vol-default
      provisioner: kubernetes.io/glusterfs
      parameters:resturl: "http://192.168.10.100:8080"restuser: ""secretNamespace: ""secretName: ""
      allowVolumeExpansion: true
      
    • 直接扩容底层的PV:
      • 重置包含文件系统的volume

        • 文件系统是XFS, Ext3, or Ext4.
        • 只有当pvc在ReadWrite模式,只有pod重新创建时才生效,因此扩容后必须删除或者重建pod。可以使用kubectl describe pvc <pvc_name>,If the PersistentVolumeClaim has the status FileSystemResizePending, it is safe to recreate the pod using the PersistentVolumeClaim
      • 重置在使用的pvc
        • FEATURE STATE: Kubernetes v1.11 alpha
        • To use it, enable the ExpandInUsePersistentVolumes feature gate
        • 会立即生效,但是对PVCs that are not in use by a Pod or deployment是没有效果的。

(5)storage class

a、简介

    一个StorageClass为管理员描述他们供应的存储类型提供了方式。不同的类型依赖管理员映射不同的服务等级、备份策略、属性策略等等。

b、StorageClass资源

    每个StorageClass包含provisioner, parameters, and reclaimPolicy字段,这些字段将被动态的提供在属于它的PV上。
    StorageClass 对象的名字是一个标志,也是用户请求一个独有的类的方式。管理员在第一次创建时可以为这个StorageClass设置这个名字或者其他参数,一旦这个对象呗创建将不能更新。
    管理员可以为没有指定绑定什么类的pvc指定默认的StorageClass进行绑定

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:name: standard
provisioner: kubernetes.io/aws-ebs
parameters:type: gp2
reclaimPolicy: Retain
mountOptions:- debug
Provisioner
  • 常见的:

    Volume Plugin Internal Provisioner Config Example
    CephFS no 外部提供者参考
    Cinder yes 后面有例子
    Glusterfs yes 后面有例子
    iSCSI no 外部提供者参考
    NFS no 外部提供者参考
    RBD(Ceph RBD) yes 后面有例子
    Local no 后面有例子
  • Reclaim Policy

    • Retain
    • Delete(默认)
  • 挂载参数

    • 需要插件支持,使用mountOptions字段定义
    • 只要一个参数写错或者不支持都将导致pv挂载失败
parameters
  • Glusterfs

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:name: slow
    provisioner: kubernetes.io/glusterfs
    parameters:resturl: "http://127.0.0.1:8081"clusterid: "630372ccdc720a92c681fb928f27b53f"restauthenabled: "true"restuser: "admin"secretNamespace: "default"secretName: "heketi-secret"gidMin: "40000"gidMax: "50000"volumetype: "replicate:3"
    
    • resturl:Gluster REST service/Heketi service url
    • restauthenabled:是否开启认证
    • restuser:
    • restuserkey:
    • secretNamespace, secretName:
    • clusterid:is the ID of the cluster which will be used by Heketi
    • gidMin, gidMax:A unique value (GID) in this range ( gidMin-gidMax ) will be used for dynamically provisioned volumes.between 2000-2147483647 default
    • volumetype,例如:
      • ‘Replica volume’: volumetype: replicate:3 其中 ‘3’ 是 replica 数量. - - ‘Disperse/EC volume’: volumetype: disperse:4:2 其中 ‘4’ 是数据,’2’ 是冗余数量.
      • ‘Distribute volume’: volumetype: none
  • OpenStack Cinder
    kind: StorageClass
    apiVersion: storage.k8s.io/v1
    metadata:name: gold
    provisioner: kubernetes.io/cinder
    parameters:type: fastavailability: nova
    
    • type:在 Cinder 中创建的 VolumeType。默认为空。
    • availability:可用区域。如果没有指定,通常卷会在 Kubernetes 集群节点所在的活动区域中轮询调度(round-robin)分配
  • Ceph RBD
    kind: StorageClass
    apiVersion: storage.k8s.io/v1
    metadata:name: fast
    provisioner: kubernetes.io/rbd
    parameters:monitors: 10.16.153.105:6789adminId: kubeadminSecretName: ceph-secretadminSecretNamespace: kube-systempool: kubeuserId: kubeuserSecretName: ceph-secret-userfsType: ext4imageFormat: "2"imageFeatures: "layering"
    
    • monitors:Ceph monitor,逗号分隔。该参数是必需的。
    • adminId:Ceph 客户端 ID,用于在池(ceph pool)中创建映像。 默认是 “admin”。
    • adminSecretNamespace:adminSecret 的 namespace。默认是 “default”。
    • adminSecret:adminId 的 Secret 名称。该参数是必需的。 提供的 secret 必须有值为 “kubernetes.io/rbd” 的 type 参数。
    • pool: Ceph RBD 池. 默认是 “rbd”。
    • userId:Ceph 客户端 ID,用于映射 RBD 镜像(RBD image)。默认与 adminId 相同。
    • userSecretName:用于映射 RBD 镜像的 userId 的 Ceph Secret 的名字。 它必须与 PVC 存在于相同的 namespace 中。该参数是必需的。 提供的 secret 必须具有值为 “kubernetes.io/rbd” 的 type 参数,例如以这样的方式创建:
      kubectl create secret generic ceph-secret --type="kubernetes.io/rbd" \--from-literal=key='QVFEQ1pMdFhPUnQrSmhBQUFYaERWNHJsZ3BsMmNjcDR6RFZST0E9PQ==' \--namespace=kube-system
      
    • fsType:Kubernetes 支持的 fsType。默认:“ext4”。
    • imageFormat:Ceph RBD 镜像格式,”1” 或者 “2”。默认值是 “1”。
    • imageFeatures:这个参数是可选的,只能在你将 imageFormat 设置为 “2” 才使用。 目前支持的功能只是 layering。 默认是 ““,没有功能打开
  • Local
    kind: StorageClass
    apiVersion: storage.k8s.io/v1
    metadata:name: local-storage
    provisioner: kubernetes.io/no-provisioner
    volumeBindingMode: WaitForFirstConsumer
    
    • Local volumes do not support dynamic provisioning yet, however a StorageClass should still be created to delay volume binding until pod scheduling. This is specified by the WaitForFirstConsumer volume binding mode.
    • Delaying volume binding allows the scheduler to consider all of a pod’s scheduling constraints when choosing an appropriate PersistentVolume for a PersistentVolumeClaim

(6)Dynamic Volume Provisioning

  • 参考:https://v1-11.docs.kubernetes.io/docs/concepts/storage/dynamic-provisioning/
  • pv的recycle中提到的就是这个,当然这个不仅仅是pv的recycle功能,其提供了自动创建pv、销毁pv的功能

kubernetes系列二:概念梳理相关推荐

  1. C#基础知识梳理系列二:C#的演绎大师:类型

    C#基础知识梳理系列二:C#的演绎大师:类型 摘 要 如果说C#是CLR特邀演员阵容之一,那类型class绝对是C#的演绎/演艺大师.不朽灵魂!它不仅演绎了C#的豪放,也演艺了C#的柔美.时而恢弘.时 ...

  2. Android Gradle的基本概念梳理(二)

    前言 逐步整理的一系列的总结: Android Gradle插件开发初次交手(一) Android Gradle的基本概念梳理(二) Android 自定义Gradle插件的完整流程(三)       ...

  3. 【云原生 | Kubernetes 系列】---Skywalking部署和监控

    [云原生 | Kubernetes 系列]-Skywalking部署和监控 1. 分布式链路追踪概念 在较大的web集群和微服务环境中,客户端的一次请求可能需要经过多个不同的模块,多个不同中间件,多个 ...

  4. 自动控制原理概念梳理(保研面试用)

    自控上下两册,常见概念及知识点整理(保研面试用). 目录 第1章 自动控制系统的一般概念 第2章 控制系统的数学模型 第3章 控制系统的时域分析 第4章 控制系统的根轨迹分析法 第5章 控制系统的频域 ...

  5. 数据与广告系列二十一:关于品牌广告,奔涌吧后浪

    作者·黄崇远 公号『数据虫巢』 全文6378字 题图ssyer.com " 看似大愚实为大智的品牌广告." 哪怕是广告行业从合约时代稳定的步入到效果广告时代,甚至是现在已经逐步的往 ...

  6. 图层几何学 -- iOS Core Animation 系列二

    <图层树和寄宿图 -- iOS Core Animation 系列一>介绍了图层的基础知识和一些属性方法.这篇主要内容是学习下图层在父图层上怎么控制位置和尺寸的. 1.布局 首先看一张例图 ...

  7. 微服务架构系列二:密码强度评测的实现与实验

    本文是继<微服务架构系列一:关键技术与原理研究>的后续,系列一中论述了微服务研究的背景和意义,主要调研了传统架构的发展以及存在的问题和微服务架构的由来,然后针对微服务架构的设计原则.容器技 ...

  8. 【JAVA编码】 JAVA字符编码系列二:Unicode,ISO-8859,GBK,UTF-8编码及相互转换

    http://blog.csdn.net/qinysong/article/details/1179489 这两天抽时间又总结/整理了一下各种编码的实际编码方式,和在Java应用中的使用情况,在这里记 ...

  9. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

最新文章

  1. 35岁,一个尴尬的年纪
  2. Python 运行 Python hello.py 出错,提示: File stdin , line 1
  3. 6.0的版本的 tc,不支持大漠对象做数组吗?
  4. 阿里云服务器CentOS6.9 nexus私服使用
  5. Qt翻译相关类之QDataStream
  6. IBASE hierarchy structure and related API
  7. 图片网站用什么服务器好,网站图片与框架放在不同服务器有哪些优缺点
  8. php中正侧表达式_PHP中正则表达式详解(代码实例)
  9. CSS实现标题文字过长部分显示省略号的方法
  10. HDU1829 A Bug's Life 并查集
  11. 嵌入式ARM体系结构总结
  12. 湘源控规7.0安装 详细图文教程
  13. 电脑如何录屏?分享4个屏幕录制的好方法,建议收藏
  14. 前端学习第三站——Vue2基础篇
  15. 【jzoj4598】【准备食物】【字典树】
  16. Elasticsearch索引新增字段
  17. java pdf工具类_Java PDF工具类(一)| 使用 itextpdf 根据PDF模板生成PDF(文字和图片)...
  18. 永磁同步电机自抗扰无位置传感器控制仿真,同时实现自抗扰和基于eso扩张状态观测器的无位置控制仿真
  19. Rosalind第28题——ros_bio28_PROB
  20. 网站导航(TreeView 控件)

热门文章

  1. sso统一认证postMessage无感处理
  2. MyBatis更新语句返回值
  3. 高德Api绘制线路轨迹
  4. 金蝶云苍穹笔记2022-07-13
  5. catia高级拔模_CATIA双侧拔模方法
  6. 如何知道程序崩溃原因?
  7. 如何在Android中取得当前进程名
  8. 微软CEO纳德拉:AI是人类大脑的加速器
  9. 记一次 .NET某家装ERP 内存暴涨分析
  10. php 开启 scandir,解决PHP环境Warning: scandir()问题