kubernetes权威指南阅读笔记——第一、二章

时间:Nov. 12, 2017 分类:

目录:

本笔记参考kubernetes权威指南第二版。

kubernetes是什么(1.1)

是什么

kubernetes是一个基于容器技术的分布式架构,是谷歌的Borg技术的一个开源版本。

kubernetes不限定任何的编程接口,统一都会被映射为kubernetes的service,通过标准的TCP通信协议进行交互。

kubernetes具备完整集群管理能力,包括多层次的安全防护机制和准入机制,多租户应用支撑,透明的服务注册和服务发现机制,内建只能负载均衡器,强大的故障发现和自我修复能力,服务的滚动升级和在线扩容能力,可扩展的资源自动调动机制,以及多粒度的资源配额管理能力。

原理

service概念

kubernetes基于Service这个核心概念提供服务,特征是:

  1. 拥有唯一名字;
  2. 拥有虚拟IP(Cluster IP,Service IP或VIP)和端口号;
  3. 提供某种远程服务能力;
  4. 被映射到提供服务能力的一组容器。

Service通过Socket通信的方式对外提供服务,每个Service都会有一个独立的Endpoint的IP和端口的访问点。kubernetes正是通过Service(虚拟Cluster IP+Service Port)连接到指定的Service上,再通过kubernetes内建的负载均衡器和故障恢复机制,就可以不论后端多少服务进程,服务进程是否会发生故障而进行重新部署,都不会影响服务的正常调度,而服务在创建后就不会变化。

pod概念

kubernetes设计了Pod,每个服务进程都包装为Pod,使其成为运行在Pod中的容器。

而Pod又运行在Node上,这个Node可以是物理机也可以是云主机。

每个Node上可以运行很多的Pod,每个Pod肯定会运行一个Pause的容器和一到多个业务容器。业务容器共享Pause容器的网络栈和Valume挂载卷。

Pod和其内部运行容器不一定需要映射到Service,只有对内部或对外部提供服务的Pod才会被映射到Service。

Pod和Service通过Pod上的Label和Service的Label Seletor进行关联。

kubernetes集群

集群管理方面Master节点和多个Node节点,Master节点上运行集群管理的一组进程kube-apiserver,kube-controller-manager和kube-scheduler,这些进程实现资源管理,Pod调度,弹性伸缩,安全控制,系统监控和纠错等功能,Node

业务扩容和服务升级

业务扩容就依赖ReplicationController(RC),RC需要三个重要的指标:

  1. Pod的定义
  2. Pod需要运行的副本数
  3. 需要监控的Pod标签

创建好的RC系统会将自动创建好的Pod,kubernetes通过RC中定义的Label筛选出对应的Pod进行实时监控,而升级也会通过RC来完成。

为什么要使用kubernetes(1.2)

由于Docker自身的优势。

kubernetes作为被业内认可的Docker分布式解决方案,实现微服务架构。微服务架构的核心是一个巨大的单体应用切分为多个很小的互相连接的微服务,一个微服务背后是多个实例的副本在进行支撑,副本数量可以根据系统的负荷进行调整,内嵌的负载均衡器自动实现调度。

我的理解是,kubernetes可以横向扩展node数量,并且可以实现秒级的业务横向扩展,对于电商行业秒杀,拼团等等流量徒增时间和量不确定,以及大促期间需要整体进行扩容有极大的帮助,我的计划是业务统一跑kubernetes上,而后端数据库都统一依赖云平台数据库服务,物理机或云主机自建。

kubernetes简单例子(1.3)

kubernetes概念介绍(1.4)

Kubernetes内很多都可以看做一种资源对象,而几乎所有所有资源对象都能通过kubectl或者API远程调用执行增删查改等操作并保存在etcd中持久化存储。

而kubernetes作为一个高度自动化的资源控制系统,通过追踪对比etcd库中保存的资源期望状态与当前环境的实际状态的差异进行控制。

master节点(1.4.1)

  • kubernetes api server提供了http rest接口的关键服务进程,是kubernetes里所有资源增删改查等操作的唯一入口,也是集群控制的进程入口;
  • kubernetes controller manager是kubernetes里所有资源对象的自动化控制中心;
  • kubernetes scheduler负责资源调度Pod调度的进程。

以上是master节点的所有服务。

node节点(1.4.2)

在kubernetes集群中,除了master节点,其他节点都被称为node节点,在早期版本被称为minion,在etcd中存储的时候也是以minion的方式存储的,node是kubernetes集群的工作负载节点,每个node都会被master分配一些工作负载(docker容器),其上的工作负载会被master主动转移到其他节点上去。

  • kubelet负责Pod对应的容器的创建,启停等任务,同时与matser节点密切协作,实现集群管理基本功能;
  • kube-proxy实现kubernetes service的通信与负载均衡机制;
  • docker engine docker引擎,负责本机的容器创建和管理。

以上是node节点上的服务

  • node节点会动态的添加到kubernetes集群,由kubelet负责向master进行注册;
  • 注册成功后kubelet会向master汇报自身情况,包括操作系统,docker版本,机器cpu和内存使用
  • master会根据Node资源使用进行调度
  • 如果node长时间不上报,master会判断node失联,状态会变为Not ready,master会触发工作负载转移的流程。

查看集群中Node

kubectl get nodes

查看Node详细信息

kubectl describe node node_name

Pod(1.4.3)

pod是kubernetes的重要概念,每个pod都有一个根容器pause容器,该容器属于kubernetes平台的一部分,除此之外pod还包含一个或多个业务容器。

kubernetes为什么会有pod的概念?

  1. 一组容器作为一个单元,我们不好对整体进行判断和有效的操作,如果一个容器宕机,是否看做整体宕机,而pod的状态代表整个容器组的状态;
  2. Pod的多个容器使用pause容器的ip,共享挂载在pause容器的valume,简化了关联容器之间的通信,并很好解决的文件共享问题。

总体来讲就是解决了服务状态和服务依赖的存储和网络问题。

pod的种类

pod其实有两种类型,一种是普通的pod和静态的pod,后者不存在kubernetes的etcd存储中,而是存放在node上的一个具体文件中,并且只能在此node运行,而普通的pod一旦被创建,就会被放到etcd中存储,并且被kubernetes master调度到某个node上进行绑定,然后pod被对应的node上的kubelet进程实例化为相关docker容器并启动,当pod中容器停止时,kubernetes会监测到这个问题并重启这个pod(重启pod中所有的容器),如果pod所在node宕机,会将node上所有pod调度到其他node上。

对于静态Pod我目前还没找到为啥要使用。

Pod的网络问题

kubernetes为每一个pod分配了ip地址,这个地址也被称为pod的ip,只需要实现底层网络支持集群内两个pod之间tcp/ip之间通信,通常采用虚拟二层网络技术实现,例如flannel和openswitch。

我的理解是,这个ip其实是pod的ip,而pod的ip是由docker引擎分配的,但是由于docker引擎的网络本事是用过docker0网卡,默认情况下是不支持node之间通信的,所以需要使用其他的方式来完成docker0网卡的通信,也就是pod的通信。

pod的存储

pod的valume是定义在pod上,然后被各个容器挂载到各自的文件系统中。

pod的事件

pod的event记录了事件的最早产生时间,最后重现时间,重复次数,发起,类型,以及造成此时间的原因。

pod配置

apiVersion: v1
kind: Pod
metadata:
  name: myweb
  labels:
    name: myweb
spec:
  containers:
    - name: myweb
      image: kubeguide/tomcat-app:v1
      ports:
      - containerPort: 8080
      env:
      - name: MYSQL_SERVICE_HOST
        value: 'mysql'
      - name: MYSQL_SERVICE_PORT
        value: '3306'
  • kind表明这是一个Pod定义
  • metadata里name是Pod的名字,并且该Pod还有一个name=myweb的标签
  • spec用来定义pod中包含的容器组,这里只定义了name(名称)为mysql,image(镜像)为kubeguide/tomcat-app:v1,ports(端口)在8080端口上启动进程,注入了env(环境变量)

这里Pod的IP和容器端口就构成了Endpoint,代表一个Pod的一个服务进程对外通信地址,一个Pod可有多个Endpoint。

pod的资源限制

每个pod可以对使用的服务器上的资源设置限额,当前可以限制计算资源,包含cpu和内存,cpu的计算资源为核数,是一个绝对值。

一个CPU配额以千分之一的CPU配额为最小单位,用m表示,通常一个容器的cpu被定义为100~300m,即占用0.1~0.3个CPU,由于CPU的配额是一个绝对值,所以在1core机器上还是48core核机器上,100m这个配额代表的CPU使用量都是一样的,内存和CPU的配置是一样的也为绝对值,单位为内存字节数。

kubernetes需要对计算资源的限定必须指定两个参数

  • requests: 该资源的最小申请量,系统必须满足要求。
  • Limits: 该资源最大允许使用的量,不能突破这个限制,当容器试图去使用超过这个量的资源时,可能会被kubernetes kill并重启。

通常requests会被设置为一个较小的值,符合容器平时工作负载情况下资源需求,而把Limit设置为峰值负载情况下资源占用的最大量。

spec:
  containers:
  - name: db
    image: mysql
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Label(1.4.4)

Lable是一个key-value的键值对,需要由用户定义,附加到资源上,例如node,pod,service,rc等,每个资源可以有任意数量的label,同一个label也可以附加到任意的资源对象上,可以在资源创建的时候创建,也可以在资源创建的时候定义,也可以在创建完成后动态添加和删除。

对资源创建一个或多个Label可以实现多维度的资源分组管理功能,这样可以更方便的进行资源分配,调度,配置和部署,然后通过Label Seletor查询或者筛选具有某些label的资源。

Label Seletor的两种表达式

基于等式

name = redis-slave

匹配具有name = redis-slave标签的资源。

env != production

匹配所有不具有标签env=production标签的资源对象,例如env=test

基于集合

name in (redis-master,redis-slave)

匹配具有标签name=redis-master或者name=redis-slave的资源对象

name not in (php-frontend)

匹配不具有name=php-frontend

多个表达式同时使用

多个表达式之间可以用','隔开,几个条件之间为AND的关系

name notin (php-frontend),env!=production

Label Seletor在kubernetes的应用场景

  1. kube-controller进程通过对资源对象RC上定义的Label Seletor来筛选需要监控的Pod副本数量,进而实现Pod副本数量始终符合预期设定数量;
  2. kube-proxy进程通过Service的Label Seletor来选择对应的Pod,自动建立起对每个Service到对应Pod的请求转发路由表,进而实现Service的智能负载均衡;
  3. 通过对Node定义特定的Label,并在Pod上使用Label上使用NodeSeletor标签调度策略,kube-scheduler进程可以实现Pod定向调度。

Replication Controller(RC)(1.4.5)

RC实际是定义一个期望场景,即声明某种Pod的副本数量在任意时刻都符合预期值,包括:

  1. Pod期望的副本数
  2. 用于筛选目标Pod的Label Seletor
  3. 当Pod副本数量小于某个预期值,用于创建Pod的Pod模板

当定义一个RC并提交到Kubernetes集群,Master节点上的Controller Manager组件就得到通知,定期巡检系统中存活的pod数量,并确保Pod数量符合期望值,超过预期值则停止多余的Pod,低于预期值则自动创建一些Pod,实现了集群的高可用性。

对于pod意外终止,会在其他node节点上创建pod。

创建RC

apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  replicas: 1
  selector:
    name: frontend
  template:
    metadata:
      labels:
        name: frontend
    spec:
      containers:
      - name: frontend
        image: kubeguide/guestbook-php-frontend
        env: 
        - name: GET_HOSTS_FROM
          value: env
        ports:
        - containerPort: 80 
  • kind表明这是定义一个ReplicationController
  • metadata元数据信息name(名字)为frontend,label(标签)为name=frontend
  • spec为RC包含的信息,replicas(副本数),selector(选择Pod的标签)和spec.template(模板,即Pod)
  • spec.template和Pod的配置是一样的,但是没有name

修改RC副本数

在运行的过程中,可以修改RC的副本数量实现对Pod的动态缩放

[root@why-01 ~]# kubectl get rc | grep redis-slave
redis-slave    2         2         2         3d
[root@why-01 ~]# kubectl scale rc redis-slave --replicas=3
replicationcontroller "redis-slave" scaled
[root@why-01 ~]# kubectl get rc | grep redis-slave
redis-slave    3         3         3         3d

另外需要注意的是,删除RC,并不会影响由RC创建得Pod,可以指定replicas为0,另外kubectl提供了stop和delete命令来一次性删除RC和RC控制的全部Pod。

[root@why-01 ~]# kubectl stop rc frontend
Command "stop" is deprecated, use "delete" instead.
replicationcontroller "frontend" deleted
[root@why-01 ~]# kubectl get rc | grep frontend
[root@why-01 ~]# kubectl get pod | grep frontend

Pod滚动升级

当滚动升级的时,也通过RC。我们需要build一个新镜像,采用新镜像版本来代替旧版本镜像,升级的时候停止一个旧版本Pod并新建一个新版本Pod,此消彼长的进行更新,当RC内所有Pod都是新版本,更新完成。

ReplicaSet

对于Replication Controller与Kubernetes的模块同名,在Kubernetes1.2版本升级为Replica Set,和RC的区别是,Replica Sets支持基于集合的Label Seletor,而RC只支持基于等式的Label Seletor。

示例

apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      name: frontend
    matchExpressions:
      - {key: name, operator: In, values: [frontend] }
  template:
    metadata:
      labels:
        name: frontend
    spec:
      containers:
      - name: frontend
        image: kubeguide/guestbook-php-frontend
        env:
        - name: GET_HOSTS_FROM
          value: env
        ports:
        - containerPort: 80

有个疑问,这个基于集合的Label选择器如何同时设置多个?

Replica Sets很少被单独的使用,只要被Deployment这个更高层的资源使用,形成一套Pod创建,删除和更新的编排活动,而Replica Sets和Deployment逐步替换了RC的作用。

Deployment(1.4.6)

Deployment是Kubernetes1.2版本的新概念,目的是为了更好的解决Pod编排问题,在Deployment内部使用了Replica Sets,来实现。

Deployment对RC的升级是可以知道Pod部署的进度。

使用场景

  • 完成指定数量的Pod创建
  • 通过查看Deploy状态来看部署Pod是否完成
  • 更新Deployment来进行镜像升级
  • 回滚到上一个版本
  • 挂起或恢复一个Deployment

创建示例

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      name: frontend
  template:
    metadata:
      labels:
        name: frontend
    spec:
      containers:
      - name: frontend
        image: kubeguide/guestbook-php-frontend
        env:
        - name: GET_HOSTS_FROM
          value: env
        ports:
        - containerPort: 80

查看状态

[root@why-01 ~]# kubectl get deployment frontend 
NAME       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
frontend   1         1         1            1           38m

输出

  • DESIRED副本的期望值,即Deployment里定义的Replica
  • CURRENT当前副本值,在Deployment创建的时候会不断增加直到DESIRED
  • UP-TO-DATE最新版本的副本数量,用于指示滚动升级的过程中有多少个副本已经成功升级
  • AVAILABLE当前集群中可用副本数

目前看起来有点鸡肋。

Horizontal Pod Autoscaler(HPA)(1.4.7)

该功能于1.1版本发布,在1.2版本成为稳定版(apiVersion: autoscaling/v1),支持Pod的横向扩容,通过动态分析RC控制的所有目标Pod的负载变化情况来针对性的调整Pod的副本数。

HPA有两个负载度量指标

  1. CPUUtilizationPercentage
  2. 应用程序自定义度量指标,例如服务每秒的请求数(TPS或QPS)

对于CPUUtilizationPercentage

  • CPUUtilizationPercentage是一个算数平均值,即目标Pod所有副本的CPU使用率的平均值。
  • CPU利用率是该Pod当前的使用量除以它的Pod Request的值
  • 当某一时刻CPUUtilizationPercentage超过限制,就会自动进行扩容,进而降低CPU利用率,当请求高峰过去的时候,CPU利用率降下来,Pod副本数会恢复正常的水平。
  • CPUUtilizationPersetage计算的为一分钟内的平均值,需要通过Heapster组件实现(?这个Heapster组件是什么)
  • 如果Pod没有定义Pod Request,则无法使用CPUUtilizationPercentage

创建HPA

通过yaml

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
  namespace: default
spec:
  maxReplicas: 10
  minReplicas: 1
  scaleTargetRef:
    kind: Deployment
    name: php-apache
  targetCPUUtilizationPercentage: 90

这个HPA控制的目标对象为一个名字为php-apache的Deployment的Pod副本,这些Pod的CPUUtilizationPercentage的值超过90%时就会自动触发自动扩容行为,在扩容缩容的时候需要满足Pod的副本要介于1和10之间。

还可以通过kubectl创建

kubectl autoscale deployment frontend --cpu-percent=90 --min=1 --max=10

对于通过yaml和kubectl,发现的区别是,kubectl会检测deployment是否存在,而通过yaml创建HPA不会

删除

kubectl delete horizontalpodautoscaler frontend

Service(1.4.8)

service定义了一个服务的访问入口地址,前端应用(Pod)通过这个地址就能访问其背后的一组Pod副本组成的集群实例。Service通过Label Seletor来和后端Pod副本集群进行tcp通信。

对于每个pod都有一个独立的IP,pod会以一个IP和Port的方法提供访问,多个pod需要一个负载均衡器使用同一IP和Port提供访问。每个Node的kube-proxy进程就是提供这个功能,在内部实现负载均衡和会话保持,不过service不会共用负载均衡器IP,而是给每个Service分配了一个虚拟IP,服务调用就变成了TCP通信了。

Pod的Endpoint地址会随着Pod的销毁重新创建而改变,但是Service创建的时候分配的Cluster IP是不会改变的,Service的Name与Service的Cluster IP在内部做一个DNS映射即可

我的理解是在创建RC,RS或Deployment创建的Pod开放的端口在Pause容器上,请求会通过Pause容器到业务容器的对应端口,而service配置端口映射到Pod的端口上,pod之间通信就是通过service进行通信,如果需要对外提供访问就需要service再创建映射到Node的路由。

service创建

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  ports:
  - port: 80
  selector:
    name: frontend

正常是在ports中需要表明Pod的targetPort,如果没有指定port和targetPort的端口一致。

对于一个Service多个Endpoint

spec:
  ports:
  - port: 8080
    name: service-port
  - port: 8085
    name: service-shutdown
  selector:
    name: frontend

Service对endpoint的映射

[root@why-01 ~]# kubectl get endpoints 
NAME           ENDPOINTS                         AGE
frontend       10.1.45.3:80,10.1.93.2:80         5d
kubernetes     10.181.4.225:6443                 27d
mysql          10.1.45.5:3306                    1d
redis-master   10.1.93.12:6379                   5d
redis-slave    10.1.93.13:6379,10.1.93.15:6379   5d
[root@why-01 ~]# kubectl get service
NAME           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
frontend       10.254.180.54    <nodes>       80:30001/TCP   5d
kubernetes     10.254.0.1       <none>        443/TCP        27d
mysql          10.254.142.98    <none>        3306/TCP       1d
redis-master   10.254.191.158   <none>        6379/TCP       5d
redis-slave    10.254.20.79     <none>        6379/TCP       5d

更详细的可以

kubectl get svc frontend -o yaml

服务发现机制

大部分分布式系统是通过特定的API接口来实现服务发现的功能,但是会导致平台入侵性比较强,kubernetes通过Service唯一的Cluster IP和Cluster name,然后通过Linux的环境变量的方式,在每个Pod启动的时候,注入环境变量,对于多个端口,根据端口名和Cluster name共同定义。

[root@why-01 ~]# kubectl exec -it redis-slave-hx6p4  -- /bin/bash
root@redis-slave-hx6p4:/data# printenv | grep REDIS_MASTER | sort
REDIS_MASTER_PORT=tcp://10.254.191.158:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.254.191.158:6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.254.191.158
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_SERVICE_HOST=10.254.191.158
REDIS_MASTER_SERVICE_PORT=6379

这些环境变量都是有命名规范的,遵循这个命名规范,代码通过访问环境变量的方式获取到所需的信息,实现服务调用。

当然kubernetes通过Add-on增值包的方式引入了DNS系统,把服务作为域名,这样就能直接用service名来做通信了。(这个Add-on增值包是什么?)

(?我在想一个问题,nginx日志中会存储那个IP地址)

外部系统访问Service

kubernetes内部有三种IP:

  • Node IP: Node节点的IP
  • Pod IP:Pod的IP
  • Cluster IP:Service的IP

详解

  • Node IP是每个节点的物理网卡的IP,是一个真实存在的网络,服务器之间通信通过该IP地址
  • Pod IP是每个Pod的IP地址,是docker engine根据docker0网桥地址段进行分配的,通常是一个虚拟的二层网络,kubernetes中一个Pod中的容器访问另一个Pod中的容器,就是通过虚拟二层网络进行通信,而真实的TCP/IP流量是通过Node IP所在网卡流出的
  • Cluster IP是一个虚拟IP,仅作用于Service对象,无法被ping,只能根据Service Port组成一个具体的通信端口,单独的Cluster IP是不具备TCP/IP通信的基础,与Node IP和Pod IP之间通信使用的是以编程方式实现的一种特殊的路由技术

在配置的时候就做一下扩展

apiVersion: v1
kind: Service
metadata:
  name: frontend
  labels:
    name: frontend
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30001
  selector:
    name: frontend

如果需要开启指定IP上进行监听,就写成Node IP+Node Port的形式。

NodePort没有完全解决外部访问的问题,比如多个Node提供访问,还是需要一个负载均衡器进行流量转发。我的理解,包括我也做过测试,不指定指定IP监听,每个Node IP+Node Port都可以进行访问,不论该Node是否有对应的Pod,所以这边Node前端的负载应该如何去做呢?

谷歌云(GCE)提供了type=LoadBalancer,此时kubernetes会自动创建一个对应的Load balancer实例并返回它的IP地址供客户端使用,其他公有云需要支持此特性的驱动,裸机上驱动在开发中。这个不知道目前有没有可能解决我上边的问题。

Valume(1.4.9)

Volume是Pod中能够被多个容器访问的共享目录,这个概念在Kubernetes和Docker中是类似的,Kubernetes的Volume被定义在Pod上,然后被Pod中的容器挂载,Volume的生命周期与Pod相同,Kubernetes支持多种类型的Volume,例如GlusterFS,Ceph等先进的分布式文件系统。

需要在Pod上声明Volume,然后在容器引入Volume并mount到指定的目录

spec:
  volumes:
    - name: datavol
      emptyDir: {}
  containers:
  - name: 
    image:
    volumeMounts: 
      - mountPath: /mydata-data
        name: datavol
    imagePullPolicy: IfNotPresent

valume实现了一个Pod中多个容器共享文件,容器数据写入磁盘或网络存储,容器配置集中化管理(ConfigMap)

valume类型

emptyDir

emptyDir Volume是Pod分配到Node的时候创建的,初始内容为空,无需指定宿主机上对应的目录,Kubernetes会自动分配一个目录,当Pod从Node上移除的时候,emptyDir中的数据也会被永久删除。

用途:

  • 临时空间,用于某些应用程序运行的临时目录,无需永久保留
  • 长时间任务的中间过程CheckPoint的临时保存目录
  • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)

目前无法控制emptyDir使用什么介质,如果kubelet配置的使用硬盘就是硬盘,日后Pod可以设置使用硬盘,ssd或者基于内存的tmpfs

hostPath

hostPath为在Pod上挂载宿主机上的文件或目录

用途:

  • 容器应用程序生成日志文件需要永久保存
  • 需要访问宿主机上Docker引擎内部数据结构的容器应用,可以定义hostPath为宿主机的/var/lib/docker目录,使容器内部应用可以直接访问docker的文件系统(不理解)

需要注意 在不同的Node上具有相同配置的Pod可能会因为宿主机上的目录和文件不同而导致对Volume上的目录和文件访问结果不一致 如果使用的资源配额管理,则Kubernetes无法将hostPath在宿主机上使用的资源纳入管理

volumes:
  - name: "persistent-storage"
    hostpast: 
      path: "/data/test_path"

gcePersistenrDisk

谷歌云提供的磁盘,参考37页

awsElasticBlockStore

亚马逊云的EBS磁盘,参考38页

NFS

volumes:
  - name: nfs
    nfs:
      server: nfs-server.localhost
      path: '/'

server为nfs server的IP地址

?待测试

其他类型的Volume

iscsi,flocker,GlusterFS,rbd,gitRepo和secret

Persistent Volume(1.4.10)

Volume被定义在Pod上,属于计算资源的一部分,而网络存储是相对独立于计算资源而存在的实体资源,例如虚拟机使用的时候,通常会定义一个网络存储,然后从中划分出一个网盘并挂载到虚拟机上Persistent Volume(简称PV)和与之关联的Persistent Volume Claim(简称PVC)也起到相同的作用。

PV和Volume的区别

  1. PV只能是网络存储,不属于任何一个Node
  2. PV不定义在Pod上,而是独立于Pod定义的
  3. PV的类型有GCE Persistent Dicks,NFS,RBD,iSCSCI,AWS EBS,GlusterFS

创建一个PV

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0003
spec: 
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /
    server: 10.181.4.225

刚才的nfs是映射到Pod的pause容器进而通过容器挂载的方式挂载到容器,而这次是直接创建PV。

accessModes的属性

ReadWriteOnce: 读写权限,并且只能被一个Node挂载 ReadOnlyMany: 只读权限,允许被多个Node挂载 ReadWriteMany: 读写权限,允许被多个Node挂载

挂载PV

定义PVC对象

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteMany
  resources:
    - requests:
        storage: 8Gi

定义到Pod实现挂载

volumes:
  - name: my
    persistentVolumeCalim:
      claimName: myclaim

PV的挂载状态

  • Available:空闲
  • Bound:已经绑定到某个PVC
  • Released:对应PVC已经删除,但是资源没有被回收
  • Failed:PV自动回收失败

我这边觉得我这边应该不会使用这个系统,我应该是使用宿主机的目录hostPath的方式,一般一个Pod会需要代码目录,配置目录和日志目录吧。

Namespace(1.4.11)

Namespace是kubernetes用于实现多租户的资源隔离,将集群内部的容器对象分配到不同的Namespace中,形成逻辑上不同项目的分组,便于共享使用整个集群资源和分别管理。

在创建Kubernetes集群的时候默认会创建一个default的Namespace,如果定义需要在metadata中添加namespace: development

查看的时候非默认namespace下资源需要指定namespace

kubectl get pods --namespace=development

对于不同的Namespace不进资源隔离,还能限定不同租户的资源,如CPU和内存使用量。

对于我的业务场景,可能只需要定义一个kubernetes system的Namespace,业务统一放在一个Namespace,需要考虑我的测试环境,灰度环境和生产环境,另外如果是一个大的kubernetes集群,如果一个集群宕机业务整体宕机,另外kubernetes的master高可用解决方案?

Annotain(注释)(1.4.12)

注释与Label类似,使用key-value的形式,可以用来记录资源对象的信息。

  • build信息,release信息,docker镜像信息:时间戳,release id号, PR号,镜像hash值,docker registry地址
  • 日志库,监控库,分析库等资源信息
  • 程序调试工具信息,例如工具名称,版本号
  • 团队信息,负责人,电话,域名,接口信息

这个可以帮助运维找到相关负责人,这个是个目前运维过程中经常遇到的,尤其业务多,运维人员少的情况。

kubernetes安装与配置(2.1)

安装kubernetes(2.1.1)

书中使用二进制方式安装

配置和启动kubernetes服务(2.1.2)

服务配置

kubernetes集群的安全设置(2.1.3)

  1. 基于ca证书
  2. 基于HTTP BASE 或 TOKEN

kubernetes的版本升级(2.1.4)

版本更新的时候,对每个Node进行隔离,更新,最后更新master

  1. 获取最新的二进制包,提取服务的二进制文件
  2. 逐个隔离Node,等待在其上运行的全部容器工作完成,更新kubelet和kube-proxy服务文件,重启以上两个服务
  3. 更新Master的kube-apiserver,kube-controller-manager,kube-scheduler服务并重启

我的理解,Master不存在并不影响线上业务,待测试?如何隔离也是个问题?

内网中kubernetes相关配置(2.1.5)

  • 内网镜像仓库
  • kubelet指向内网镜像仓库

kubernetes核心服务配置详解(2.1.6)

所有配置参数,很详细的介绍

每个服务的参数也可以通过cmd --help获取,但是无法通过命令获取公共参数。

示例操作

kube-apiserver cmd --help

kubernetes集群网络配置方案(2.1.7)

  • flannel
  • Open vSwitch
  • 直接路由

?需要日后都实践一下。

kubectl命令行工具用法详解(2.2)

其实kubectl --help介绍的非常非常详细,还能自动补全。

  • kubectl用法描述(2.2.1)
  • kubectl子命令详解(2.2.2)
  • kubectl参数列表(2.2.3)
  • kubectl输出格式(2.2.4)
  • kubectl操作示例(2.2.5)

Guestbook示例:Hello World(2.3)

这边博主初识的时候就是使用的这个例子,可以参考以前的博文kubernetes集群

深入掌握Pod(2.4)

Pod定义详解(2.4.1)

概述Pod完整的配置文件和各个配置参数的详解

Pod基本用法(2.4.2)

对于docker启用一个docker run命令创建并启动一个容器,而在kubernetes系统中对于长时间运行的容器的要求是,主程序需要一直在前台执行。

如果是在后台运行的,kubelet创建包含这个容器的Pod之后运行完该命令,就认为Pod执行结束,就立刻销毁该Pod,如果Pod被定义为RC,系统监控到Pod终止后会根据RC定义的Pod的replicas副本数量生成一个新的Pod,而一旦创建出新的Pod在执行完命令后又会终止,陷入无限循环的过程中,所以创建的镜像必须以前台的方式启动。

对于无法改造成前台执行的程序,也可以使用supervisor辅助进行前台运行。

对于松耦合的业务分别放在不同的Pod中,对于紧耦合的业务放在同一个Pod中。

放在同一个Pod的容器之间可以直接通过localhost:端口的方式访问本Pod的容器

对于使用guestbook-php-frontend:localredis镜像在同一个Pod启动frontend和redis,可以看到guestbook.php文件中是使用的localhost,获取的环境变量为REDIS_HOST

?(有机会去看下这个里的环境变量,因为另外这个生产的时候需要和业务配合)

环境变量写的IP为service的IP,都在KUBE_SERVICE_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"配置的网段,?(这些环境变量是如何注入的呢,选择注入还是全部注入)

静态Pod(2.4.3)

不通过api server进行管理,无法与ReplicationController,Deployment或DaemonSet进行关联,kubelet也无法对其进行健康检查。静态Pod总是由kubelet创建,并在kubelet所在Node上运行。

创建静态的Pod

kubelet启动参数--config中指定需要监控的配置文件所在的目录,kubelet会定期扫描,进行创建和删除,在master上删除该pod也只会使pod为pending状态。

假设配置为/etc/kubelet.d/,就在启动参数指定--config=/etc/kubelet.d,然后在对应目录放置yaml文件

?P107

作用未知

Pod共享Volume(2.4.4)

同一个Pod中的多个容器能够共享Pod级别的存储卷volume,也就是都挂载Pauese容器目录的方式对多个容器进行挂载。

Pod配置管理(2.4.5)

kubenetes v1.2版本提供了ConfigMap

  1. 生成容器内环境变量
  2. 设置容器启动命令
  3. 以volume的形式挂载为容器的文件或目录

?没搞懂是做什么的

Pod生命周期和重启策略(2.4.6)

Pod的状态:

  • Pending API server已经创建Pod,但是Pod内还有容器的镜像没有创建,包括正在下载的镜像
  • Running Pod内所有容器均已创建,且至少有一个容器处于运行状态,正在启动状态或正在重启状态
  • Success Pod内所有容器均成功执行退出,且不会再重启
  • Failed Pod内所有容器均已退出,但是至少有一个容器退出为失败状态
  • Unknown 由于某种原因无法获取该Pod的状态,可能是由于网络通信不畅导致

Pod的重启策略应用于Pod内所有容器,由Pod所处Node上的kubelet进行判断和重启操作,当某个容器异常退出或者健康检查失败时,kubelet将根据RestartPolicy的设置来进行相应的操作。

Pod的重启策略:

  • Always 当容器失效时,由kubelet自动重启容器
  • OnFailure 当容器终止运行且退出码不为0时,由kubelet自动重启该容器
  • Never 不论容器运行状态如何,kubelet都不会重启该容器

kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算,例如1,2,4,8倍,最长延时5分钟,并且在成功重启后的10分钟后重置该时间。

  • RC和DaemonSet必须设置为Always,需要确保容器持续运行
  • JobOnFailure或Never,确保容器执行后不会再重启
  • kubelet在Pod失效的时候自动重启,不论RestartPolicy设置为什么值,也不会对Pod进行健康检查

常见状态转化场景

Pod包含容器数 Pod当前状态 发生事件 重启策略为Always的Pod结果状态 重启策略为OnFailure的Pod结果状态 重启策略为Never的Pod结果状态
包含一个容器 Running 容器成功退出 Running Succeeded Succeeded
包含一个容器 Running 容器失败退出 Running Running Failed
包含多个容器 Running 1个容器失败退出 Running Running Running
包含多个容器 Running 容器被OOM杀掉 Running Running Failed

Pod健康检查(2.4.7)

对Pod的健康检查通过两类探针来检查,LivenessProbe和ReadinessProde

  • LivenessProbe用于判断容器是否是running状态,如果不是则通过kubelet kill掉容器,并根据容器策略做对应的处理,如果容器不包含LivenessProbe探针,kubelet会认为LivenessProbe探针的返回值为success
  • ReadinessProbe用于判断容器是否为ready状态,可以接收请求,如果ReadinessProbe监测失败,则Pod的状态会被修改,Endpoint controller将从Service的Endpoint中删除包含该容器所在Pod的Endpoint

kubelet定期执行LivenessProbe探针来诊断容器的健康状况。

LiveneessProbe的实现方式————ExecAction

在容器内部执行一个命令,如果命令返回值为0则代表容器健康。

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness
spec:
  containers:
    - name: liveness-exec
      image: gcr.io/google_containers/busybox
      arg:
        - /bin/sh
        - -c
        - echo ok > /health; sleep 10; rm -rf /tmp/health; sleep 600
      livenessProbe:
        exec:
          command:
            - cat
            - /tmp/health
        initialDelaySeconds: 15
        timeoutSeconds: 1

LiveneessProbe的实现方式————TCPSocketAction

通过容器IP地址和端口号执行TCP检查,如果能建立TCP连接,表明容器健康。

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-healthcheck
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
      - containerPort: 80
      livenessProbe:
        tcpSocket:
          port: 80
        initialDelaySeconds: 30
        timeoutSeconds: 1

LiveneessProbe的实现方式————HTTPGetAction

通过对容器的IP地址,端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且等于400,则认为容器状态健康

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-healthcheck
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
      - containerPort: 80
      livenessProbe:
        httpGet:
          path: /_status/healths
          port: 80
        initialDelaySeconds: 30
        timeoutSeconds: 1

而对于每种探针都需要设置initialDelaySeconds和timeoutSeconds两个参数 initialDelaySeconds,启动容器后首次健康检查的等待时间,单位为秒 timeoutSeconds健康检查发送请求后等待响应的超时时间,单位为秒,当超时发生的,kebulet会认为容器已经无法提供服务,将会重启该容器

Pod调度(2.4.8)

Pod调度 在kubernetes中,Pod是容器的载体,通常需要RC,Deployment,DaemonSet,Job等对象完成Pod的调度和自动控制。

RC和Deployment:全自动调度

RC的主要功能是自动部署一个容器的多份副本,并且持续的监控副本的数量,在集群内部维持指定的副本数量。

示例创建3个Pod的RC

apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
  labels:
    name: frontend
  spec:
    replicas: 3
    selector:
      name: frontend
    template:
      metadata:
        labels:
          name: frontend
      spec:
        containers:
        - name: frontend
          image: kubeguide/guestbook-php-frontend
          env: 
            - name: GET_HOSTS_FROM
              value: env
            ports:
            - containerPort: 80

NodeSelector:定向调度

Master的Scheduler服务(kube-scheduler进程)负责实现Pod的调度,通过算法为Pod计算一个最佳的目标节点,将Pod指定到一些Node上,可以使Node的Label和Pod的nodeSeletor实现。

首先要给Node打标签

kubectl label nodes <node-name> <label-key>=<label-value>

然后在Pod的定义中加上nodeSelector的设置。

apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
  labels:
    name: frontend
  spec:
    replicas: 3
    selector:
      name: frontend
    template:
      metadata:
        labels:
          name: frontend
      spec:
        containers:
        - name: frontend
          image: kubeguide/guestbook-php-frontend
          env: 
            - name: GET_HOSTS_FROM
              value: env
            ports:
            - containerPort: 80
            nodeSelector:
              ccj_env: pro

对于多个Node都定义了相同的标签,scheduler将会根据调度算法在Node中挑选一个可用的Node进行Pod的调度。 对于Pod定义了nodeSeletor,但是集群中不存在相应Lebel的Node,即使集群中有资源空闲的Node,也不会被调度。?不调度是不创建吗?

NodeAffinity:亲和性调度

相对NodeSeletor,增加了In(属于的或运算),NotIn(不属于),Exists(存在),DoesNotExists,Gt(大于),Lt等操作符来选择Node

?没深入了解,因为我如果想调度,肯定是一个固定的调度Node,应该暂时不存在这种匹配关系,参考P128

对于同时设置了NodeSeletor和NodeAffinity,需要同时满足这两个条件。

未来还可以添加PodAffinity,用于控制当调度Pod到某个特定的Node上时,判断是否有其他Pod正在该Node上运行。

DaemonSet:特定场景调度

DaemonSet用于管理在集群中每个Node上仅运行一份Pod副本实例。

适合一些特定的需求:

  1. 在每个Node上运行一个GlusterFs存储或者Ceph存储的daemon进程
  2. 在每个Node上运行一个日志采集程序,fluentd或者logstash
  3. 在每个Node上运行一个健康程序,采集该Node的运行性能数据,例如Prometheus Node Exporter,collected,New Relic agent或者Ganalia gmod等

对于Pod调度策略与RC类似

示例挂载


?配置文件没抄,不知道这个是什么服务

是一个docker日志的解决方案

Job的批处理

?考虑到批处理场景在我们的业务场景并不存在,我没有过多了解。参考P132

Pod的扩容和缩容(2.4.9)

在实际的生产环境,会需要业务扩容和缩容的场景,也可能会遇到由于资源紧张或者工作负载降低需要减少服务实例数量的场景,可以通过RC的Scale机制完成。

手动扩容和缩容

kubectl scale rc redis-slave --replicas=3

自动扩容和缩容

?这个需要好好研究一下

Pod的滚动升级(2.4.10)

集群中某个服务需要升级的时候,需要停止目前与该服务相关的Pod,然后重新启动并拉取镜像并启动,Kubernetes提供了rolling-update滚动升级来解决上述问题。

滚动升级通过执行kubectl rolling-update命令完成,原理是创建一个新的RC,然后自动控制旧的RC中的Pod副本的数量逐渐减少到0,新的Pod副本数从0逐步添加到目标值,最终实现了Pod的升级,需要注意的是,系统要求新的RC和旧的RC在相同的namespace中。

?这个也需要好好研究

深入掌握Service(2.5)

Service是一个kubernetes最核心的概念,通过创建Service,可以为一组具有相同的容器应用提供一个统一的入口地址,并将请求进行负载分发到后端的各个容器上

Service定义(2.5.1)

  • yaml格式的service定义--143页
  • service定义的各个属性说明--144页

Service基本用法(2.5.2)

示例创建一个RC和两个tomcat容器,对外提供的端口号为8080

创建RC

apiVersion: v1
kind: ReplicationController
metadata:
  name: webapp
spec:
  replicas:
  template:
    metadata:
      name: webapp
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: tomcat
        ports:
        - containerPort: 8080

获取Pod的IP地址

kubectl get pods -l app=webapp -o yaml | grep podIP

可以通过Pod的IP地址和端口进行访问容器

创建Service

快速创建实例,通过kubectl的expose

kubectl expose rc webapp

service所需的端口号则从Pod的containerPort的端口号继承而来。

kubectl get svc 

获取到的ClusterIP和端口就也可以访问容器。

对ClusterIP+Port进行访问可以路由到两个Pod节点的任意一个。

也可以通过yaml创建

?略过

Service负载方式

默认负载

kubernetes提供了两种Service负载分发策略RoundRobin和SessionAffinity

  • RoundRobin轮询模式
  • SessionAffinity基于客户端IP地址进行会话保持

默认情况下,kubernetes使用RoundRobin进行路由选择

也可以通过配置service.spec.sessionAffinity设置为ClientIP来启动SessionAffinity策略,同一个客户端请求来会建立一个Session并且对应到后端固定的某个Pod

自定义负载

?Kubernetes也可以通过Headless Service的概念来实现不使用默认负载功能,也不提供clusterIP,而是通过Label Seletor将后端Pod列表返回给调用的客户端

配置方式在spec.clusterIP设置为None

负载到集群外部

?通过创建Endpoint方式

集群外部访问Pod或Service(2.5.3)

  1. 将容器应用的端口号映射到物理机
  2. 将Service的端口号映射到物理机

?云服务厂商的LB

DNS服务搭建(2.5.4)

Ingress:HTTP7层路由机制(2.5.5)