这边笔记涵盖了以下内容:

  • 容器虚拟化的原理
  • 容器编排的基本概念
  • k8s的容器编排对象
  • k8s的集群架构和基本原理
  • 容器编排的实践

参考学习材料

  • 《docker进阶与实战》
  • 《kubernetes in action》ch1-ch11

  • 极客时间《深入剖析Kubernetes》

容器&docker

docker的虚拟化原理

Namespace 做隔离,Cgroups 做限制,rootfs 做文件系统

本质上依然是一个进程

Namespace 做隔离

namespace用于对全局资源的隔离

已经有的:

  • ipc
  • network
  • mount
  • pid
  • uts
  • user

通常的操作:

clone: 创建新的namespance

setns:把进程放进已有的namespace里

Cgroups 做限制
  • cpuset子系统:限制可用的cpu列表

  • cpu子系统:限制cpu利用率

  • memory子系统:限制内存使用率

rootfs 做文件系统

进程使用pivot_root系统调用来做跟文件系统的切换

(union mount来分层镜像)

容器编排&kubernetes

什么是容器编排(Container Orchestration)

容器编排是为了简化服务的开发和运维,提升基础设施利用率的工具。

容器编排核心关注的是以下几个点

  1. Workloads

  2. Scheduler

  3. Networking

  4. Storage

workloads是最小的部署单元,比如我们手动在服务器上启动web服务:python manager runserver,可以认为最小的部署单元是一个进程。workloads包含了network/memory/cpu等资源的需求。

scheduler主要功能有三个:

  • 控制着workloads的生命周期,包括创建,销毁,health check
  • 控制着workloads之间的约束关系:Coordination/Replica/Dependency
  • workloads的扩容缩容/更新/回滚等

Networks关注集群的网络模型(L3/L4)/DNS/service discovery/load balance

存储主要分为volumn & persistence volumn

kubernetes是起源于google的borg&omega的容器编排工具

image-20200329213121557

kubernetes的集群架构

从硬件角度看,一个kubernetes集群有很多节点(node)组成,每个节点可以是物理机/虚拟机,这些节点被分为两种类型:

  • master:集群的管理

  • worker:运行用户实际部署的应用

image-20200329220457602

master主要有四个组件

  • etcd:提供线性一致性的存储,用于存储集群的配置信息/leader 选举
  • api server:etcd的代理,提供etcd的配置变更接口,变更监听的能力
  • scheduler:调度应用(给应用分配可部署的节点)
  • controller manager:跟踪工作节点,处理工作节点失效等集群的功能

worker nodes主要有三个组件

  • kubelet:管理节点的容器

  • kube-proxy:组件之间的负载均衡网络流量

  • Container runtime:容器运行时

kubernetes的资源对象

kubernetes的资源对象包括了pod/deployment/service等等各种各样的资源。

image-20200329221527443

kubernetes的资源对象都是通过yaml或者json描述文件来创建的,kubebctl也提供了一些简单的命令创建一组属性有限的资源对象。

1. pod - 最小的调度单位(workloads)

pod的结构

kubernetes使用pod作为最小调度单位,pod是一组共享了某些资源的容器

具体的说,共享了net/uts/ipc/pid namespace,所以具有相同的loopback网络接口,可以进行ipc通信等,但是挂载文件系统是隔离的(文件系统来自于容器镜像)

image-20200329223022331

  • pod里的容器必须运行在相同的节点,解决了成组调度的问题
  • Infra 容器是一个非常特殊的镜像,叫作:k8s.gcr.io/pause。这个镜像是一个用汇编语言编写的、永远处于“暂停”状态的容器,解压后的大小也只有 100~200 KB 左右
  • pod之间的网络没有NAT转换
  • Pod 扮演的是传统部署环境里“虚拟机”的角色,调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的
容器设计模式

主容器运行应用,sidecar进程运行住进程之外的工作

eg:

  • war && tomcat
  • 应用和日志搜集
  • istio sidecar
pod的资源描述
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1 # api版本
kind: Pod # 资源类型
metadata: # 名称/命名空间(namespace)/标签(label)
name: kubia-manual
spec: # pod内容的实际说明,例如容器/volume等
containers:
- image: luksa/kubia
name: kubia
ports:
- containerPort: 8080
protocol: TCP
# status: # status是运行时资源的状态
  • 使用命名空间对资源进行分组(没有隔离作用)

  • 使用标签和选择器来约束pod的调度

    1
    2
    3
    spec:
    nodeSelector:
    labelKey: labelValue

2. deployment - 声明式的升级应用

从replicationController(rc)说起

image-20200330003811291

通过给pod配置存活探针,kubernetes检测pod本身的状态

  • 当pod出现问题,replicationController会自动拉起一个新的pod
  • 修改replicationController的replicas,可以实现动态扩容缩容
  • 修改replicationController的template,可以升级应用(修改之后再删除原有的,因为修改template并不会影响现有的运行中的pod,这是一种停机升级)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: ReplicationController
metadata:
name: kubia
spec:
replicas: 3 # 副本数量
selector: # 根据label选择pod
app: kubia
template: # 声明被控制的对象(俄罗斯套娃
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia
ports:
- containerPort: 8080
deployment直接作用的是rs不是pod

replicationSet(rs)就是label选择表达式更加丰富了的rc。

没有deployment的时候,升级的应用的方式有:

  1. 使用一个rc,停机升级:先修改template,删除旧pod,然后创建新的pod

image-20200330005629376

  1. 使用两个rc,不停机升级:先修改template,删除旧pod,然后创建新的pod

image-20200330005650536

  1. 手动执行滚动升级

image-20200330005740322

使用了deployment实现自动滚动升级(kubernetes自动控制)

image-20200330010423544

  • deployment滚动升级过程也是通过创建新的rs逐步替换旧pod,旧的rs不删除
  • 保留旧的rs,方便回滚
  • 支持暂停升级(相当于运行了一个金丝雀版本的app)
其他的编排对象
  • daemon set: 给每个node都创建一个pod

    eg:kube-proxy,日志搜集程序,servicemesh网格/sidecar

  • job:一次性任务/定时任务

  • stateful set:有状态的pods的编排对象

其他编排方式:蓝绿/金丝雀(灰度)…

https://github.com/ContainerSolutions/k8s-deployment-strategies

3. service - 让客户端发现pod&和pod通信

service主要解决两个问题:

  1. pod随机调度引起的pod应用定位的问题
  2. pod的流量的负载均衡

service提供稳定的ip地址来访问pod

service的ip地址是虚拟ip地址(vip),无法ping通

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
ports: # 提供多个端口映射
- port: 80
targetPort: 8080
selector:
app: kubia
服务发现

主要通过环境变量和DNS用于发现服务

  • 环境变量

    XXX_SERVICE_HOST

    XXX_SERVICE_PORT

    (XXX为服务名字)

  • DNS

    {service_name}.{namespace}.{可配置的集群域后缀}

    eg:kubia.default.svc.cluster.local

连接外部服务

image-20200402213710432

连接service和pod的是一种叫做endpoint的资源,endpoint资源是一组服务的ip地址和端口列表

手动配置endpoint指向外部的ip/域名,可以使得服务得以访问相应的外部服务

对外暴露服务
nodeport

把任意集群节点的端口的流量转发到内部的service ip端口

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort
ports:
- port: 80 # service 端口
targetPort: 8080 # pod容器端口
nodePort: 30123 # 集群的节点的端口
selector:
app: kubia

image-20200402213019107

负载均衡器

在nodeport基础上做了一层负载均衡

image-20200402213121550

ingress

ingress提供多个服务的负载均衡,本质上是k8s对反向代理的一种抽象(nginx/haproxy…)

image-20200402213743308

ingress不转发请求给service,而是通过service来选择一个pod

image-20200402213913111

headless服务

通过讲服务的spec配置项clusterIP设置为none,DNS查询不返回service的vip,而是返回多个A记录(每个pod一个)

4. volume - 将磁盘挂在到pod中

docker的文件系统来自于镜像,通过volume实现pod中多个容器的文件共享

常用volume
emptyDir

磁盘临时文件,pod删除,卷内容会被回收

hostPath

挂载工作节点(host机器)上的文件,类似于shopee的日志

gitRepo

git仓库同步的目录(不能一直保持同步)

persistence volume

Pv/pvc/storage class

pvc:持久卷声明,面向用户(开发),类似于接口。是用户需求清单的抽象

pv:持久卷,面向运维,类似实现了pvc的对象,运维可以随意替换存储介质的实现

storage class:实现了pvc的类,可以通过storage class模版化的生成pv

image-20200402215322964

Projected Volume

常用配置应用程序的方式有:命令行参数/配置文件读取/环境变量读取。

projected volume是配置资源的抽象,但是最终让用户/应用进程所感知的方式同样是上面三种

抽象配置资源,可以独立配置声明的yaml文件和部署资源的yaml文件

configmap

存储非敏感的文本配置数据

secret

用于传递证书/私钥之类的敏感数据

downward api

用来和k8s通信/获取pod元数据的

ServiceAccountToken

kubernetes运行原理

组件

image-20200329213556137

k8s组件主要分为:

  • 控制面板
    • etcd
    • apiserver
    • scheduler
    • controller manager
  • 工作节点
    • kubelet
    • kube-proxy
    • 容器运行时
  • 附加组件
    • K8s DNS服务器
    • dashboard
    • ingress控制器
    • 集群监控
    • CNI(容器网络接口插件)

几个点:

  • etcd是唯一的存储组件(raft协议,线性一致性的存储系统)
  • apiserver是唯一的和etcd交互的组件(apiserver实现了乐观锁机制,能保证冲突被正常处理)
  • apiserver以外的组件之间的通信都是通过apiserver进行
  • etcd和apiserver是多实例同时运行,其他组件虽然多实例,但只有一个主节点处于工作状态(利用etcd选举)
  • 除了kubelet外,其他组件都可以作为pod来运行
1) etcd - 存储介质
2) apiserver - k8s的api,类似系统调用之于于操作系统
  1. 集群资源和状态的crud

    image-20200402223335663

  2. 资源变更的通知

image-20200402223352288

3) 调度器 - 分配节点

调度器调度流程:

  1. 调度器监听api server中所有更新的pod的定义
  2. 选择一个合适的节点(涉及到调度策略)
  3. 更改pod中nodeName的声明
  4. apiserver通知对应节点的kubelet
  5. kubelet部署pod
4) controller manager - 让集群状态向api定义的期望状态收敛

控制器包括

  • deployment controller
  • node controller
  • service controller
  • endpoints controller
  • namespace controller

有些controller依赖外部组件,比如ingress controller,就是坚挺apiserver里ingress资源的变更,然后更新ingress的基础设施(nginx/haproxy等)的配置,动态修改配置,让集群的状态变成我们期待的状态(资源声明)

5) kubelet

主要工作有:

  1. 注册node到apiserver

  2. 启动pod容器

  3. 持续监控容器,向apiserver报告

6)kube-proxy

Cluster IP与NodePort等概念是kube-proxy服务通过iptables的NAT转换实现的,kube-proxy在运行过程中动态创建与Service相关的iptables规则,这些规则实现了将访问服务(Cluster IP或NodePort)的请求负载分发到后端Pod的功能

image-20200403011056624

运行模式

image-20200402231301311

最开始用于修改iptable规则,同时作为proxy组件代理请求进出,所以叫kube-proxy(性能差, 内核和用户态之间传输数据)

image-20200402231314698

完全iptable模式,性能较高

7) 插件

通过yaml声明,作为daemonset/deployment/replicationController资源部署

控制器工作流

image-20200402231908550

所有组件执行动作的时候都会生成一个事件资源发送给apiserver,可以直接观测到

跨pod网络(service/DNS/服务发现)

k8s的网络的原则:

  1. 每个pod具有唯一的ip
  2. pod之间的通信没有NAT

image-20200402232325392

1. 同一个pod内不同的容器通信

同一个pod内的容器共享net namespace,共享协议栈,等同于在相同主机通信

2. 同一节点的通信原理(和docker的CNM一样

image-20200402232551057

桥接网络模式

Pod a ping pod b:

  1. ping进程构造ICMP echo请求包,并通过socket发给协议栈,
  2. 协议栈根据目的IP地址和系统路由表,知道去10.1.1.2的数据包应该要由10.1.1.1口出去
  3. 由于是第一次访问10.1.1.1,且目的IP和本地IP在同一个网段,所以协议栈会先发送ARP出去,询问10.1.1.1的mac地址
  4. 协议栈将ARP包交给eth0,让它发出去
  5. 由于eth0的另一端连的是veth123,所以ARP请求包就转发给了veth123
  6. 由于veth123的另一端连的是bridge,所以ARP请求包就转发给了bridge
  7. arp包继续到达beth234和podb的eth0
  8. eth0把arp包往上抛给协议栈,协议栈发现自己的设备有这个ip,返回arp应答(没有就抛弃

ps:公司使用的是host网络(容器和宿主机具有相同的ip地址

3. 跨节点的通信原理

image-20200402234517386

不同节点之间的网桥以某种形式连接

  • 三层网络

  • 二层网络

具体由CNI插件来实现

  • calico
  • flannel
    • udp
    • vxlan
    • Host-gw
UDP

image-20200402235448264

  • Docker0(br)通过host机器上的路由表,转发请求到flannel0
  • flannel0是一个tun设备,把请求转发到用户空间的flanneld程序
  • flanneld查找etcd,找到目的容器ip对应的节点ip
  • 套娃一层ip包
  • 通过宿主机etho0转发udp包到公网
vxlan

image-20200403000010564

  • 在现有的三层网络之上,“覆盖”一层虚拟的、由内核 VXLAN 模块负责维护的二层网络,使得连接在这个 VXLAN 二层网络上的“主机”(虚拟机或者容器都可以)之间,可以像在同一个局域网(LAN)里那样自由通信

  • 目的 VTEP 设备的arp表在节点创建的时候生成

host-gw

image-20200403002417215

  • 路径更短,性能更高
  • 但是节点之间必须是二层联通的(通过路由表吓一跳mac地址设置目的地址,通过二层网络传输

常用kubectl的命令(持续更新

资源信息

  1. kubectl explain po/ns …

pod

pod
  1. 创建pod - 通过yaml文件

    kubectl create -f xxx.yaml

  2. 查看运行时pod的完整定义

    kubectl get po poname -o yaml(-o 表示格式yaml/json)

  3. 查看pod日志

    kubectl logs podname (只有一个容器时候)

    kubectl logs podname -c containername (不只有一个容器时候)

  4. 本地ssh端口转发

    kubectl port-forward podname localPort:podPort

  5. 删除pod

    kubectl delete po podname

    kubectl delete po -l labelKey=labelValue

    kubectl delete ns nsName

    kubectl delete po –all

    kubectl delete all –all

label
  1. 创建标签
    • 通过命令
    • 通过yaml文件
1
2
3
4
5
xxx
metadata:
name: podname
labels:
labelkey: labelval
  1. 查看标签

    • 列表带标签

    kubectl get po –show-labels

    Kubectl get po -L labelkey1,labelkey2

    • 通过标签过滤

    Kubectl get po -l labelkey

    Kubectl get po -l labelkey=labelval

    Kubectl get po -l labelkey=’!labelval’

  2. 修改标签

    Kubectl label po podname labelkey=labbelval

    Kubectl label po podname labelkey=labbelval –overwrite(修改已存在的)

  3. 使用标签分类工作节点

    Kubectl label node codename labelkey=labelval

  4. 查看pod详细信息

    Kubectl describe po podname

  5. 添加注解

    Kubectl annotate pod podname key=value

namespace
  1. 创建命名空间

    • yaml
    1
    2
    3
    4
    apiVersion: v1
    kind: Namespace
    metadata:
    name: custom-ns
    • kubectl 方式:

      kubectl create namespace custom-ns

  2. 查看namespace

    kubectl get ns

  3. 查看namespace中的所有pods

    kubectl get po -n namespaceName