概述

Kubernetes是容器集群管理系统,是一个开源的平台,可以实现容器集群的自动化部署、自动扩缩容、维护等功能。

使用Kubernetes可以:

  • 自动化容器的部署和复制

  • 随时扩展或收缩容器规模

  • 将容器组织成组,并且提供容器间的负载均衡

  • 很容易地升级应用程序容器的新版本

  • 节省资源,优化硬件资源的使用

  • 提供容器弹性,如果容器失效就替换它,等等

功能

  1. 自动装箱:基于容器对应用运行环境的资源配置要求自动部署应用容器
  2. 自我修复:
    • 当容器失败时,会对容器进行重启
    • 当所部署的Node 节点有问题时,会对容器进行重新部署和重新调度
    • 当容器未通过监控检查时,会关闭此容器直到容器正常运行时,才会对外提供服务
  3. 水平扩展:通过简单的命令、用户UI 界面或基于CPU 等资源使用情况,对应用容器进行规模扩大
    或规模剪裁
  4. 服务发现:用户不需使用额外的服务发现机制,就能够基于Kubernetes 自身能力实现服务发现和
    负载均衡
  5. 滚动更新:可以根据应用的变化,对应用容器运行的应用,进行一次性或批量式更新
  6. 版本回退:可以根据应用部署情况,对应用容器运行的应用,进行历史版本即时回退
  7. 密钥和配置管理:在不需要重新构建镜像的情况下,可以部署和更新密钥和应用配置,类似热部署
  8. 存储编排:
    • 自动实现存储系统挂载及应用,特别对有状态应用实现数据持久化非常重要
    • 存储系统可以来自于本地目录、网络存储(NFS、Gluster、Ceph 等)、公共云存储服务
  9. 批处理:提供一次性任务,定时任务;满足批量数据处理和分析的场景

集群架构

Master与Node

master组件

  1. apiserver:集群统一入口,以RESTful方式,交给etcd存储

    Api Server负责输出RESTful风格的Kubernetes API,它是发往集群的所有REST操作命令的接入点,并负责接收、校验并响应所有的REST请求,结果状态被持久存储于etcd中。因此,API Server是整个集群的网关

  2. scheduler:节点调度,选择node节点应用部署

    Kubernetes是用于部署和管理大规模容器应用的平台,根据集群规模的不同,其托管运行的容器很可能会数以千计甚至更多。API Server确认Pod对象的创建请求之后,便需要由Scheduler根据集群内各节点的可用资源状态,以及要运行的容器的资源需求做出调度决策。另外,Kubernetes还支持用户自定义调度器

  3. controller-manager:处理集群中常规后台任务,一个资源对应一个控制器

    Kubernetes中,集群级别的大多数功能都是由几个被称为控制器的进程执行实现的,这几个进程被集成于kube-controller-manager守护进程中。由控制器完成的功能主要包括生命周期功能和API业务逻辑,具体如下:

    • 生命周期功能:包括Namespace创建和生命周期、Event垃圾回收、Pod终止相关的垃圾回收、级联垃圾回收及Node垃圾回收等

    • API业务逻辑:例如,由ReplicaSet执行的Pod扩展等

  4. etcd:存储系统,用于保存集群相关的数据

    etcd 的官方将它定位成一个可信赖的分布式键值存储服务,它能够为整个分布式集群存储一些关键数据,协助分布式集群的正常运转。

    Kubernetes集群的所有状态信息都需要持久存储于存储系统etcd中,不过,etcd是由CoreOS基于Raft协议开发的分布式键值存储,可用于服务发现、共享配置以及一致性保障(例如数据库主节点选择、分布式锁等)。因此,etcd是独立的服务组件,并不隶属于Kubernetes集群自身。生产环境中应该以etcd集群的方式运行以确保其服务可用性。

    etcd不仅能够提供键值数据存储,而且还为其提供了监听(watch)机制,用于监听和推送变更。Kubernetes集群系统中,etcd中的键值发生变化时会通知到API Server,并由其通过watch API向客户端输出。基于watch机制,Kubernetes集群的各组件实现了高效协同。

node组件

  1. kubelet:master排到node节点代表,管理本机容器,例如生命周期等

    kubelet是运行于工作节点之上的守护进程,它从API Server接收关于Pod对象的配置信息并确保它们处于期望的状态(desired state,也可以说目标状态)kubelet会在API Server上注册当前工作节点,定期向Master汇报节点资源使用情况,并通过cAdvisor监控容器和节点的资源占用情况。

  2. kube-proxy:提供网络代理,负载均衡等操作

    每个工作节点都需要运行一个kube-proxy守护进程,它能够按需为Service资源对象生成iptables或ipvs规则,从而捕获访问当前Service的ClusterIP的流量并将其转发至正确的后端Pod对象。

核心附件

  1. KubeDNS:在Kubernetes集群中调度运行提供DNS服务的Pod,同一集群中的其他pod可使用此DNS服务解决主机名。Kubernetes从1.11版本开始默认使用CoreDNS项目为集群提供服务注册和服务发现的动态名称解析服务,之前的版本中用到的是kube-dns项目,而SKyDNS则是更早一代的项目
  2. Dashboard:Kubernetes集群的全部功能都要基于Web的UI,来管理集群中应用甚至是集群自身
  3. Heapster:容器和节点的性能监控与分析系统,它收集并解析多种指标数据,如资源利用率、生命周期事件等。新版本的Kubernetes中,其功能会逐渐由Prometheus结合其他组件所取代
  4. Ingress Controller:Service是一种工作于传输层的负载均衡器,而Ingress是在应用层实现的HTTP(s)负载均衡机制。不过,Ingress资源自身不能进行“流量穿透”,它仅是一组路由规则的集合,这些规则需要通过Ingress控制器(Ingress Controller)发挥作用。目前,此类的可用项目有Nginx、Traefik、Envoy及HAProxy等

核心概念

  1. Pod:
    • 最小部署单元
    • 一组容器的集合
    • 共享网络
    • 生命周期是短暂的
  2. controller:
    • 确保预期的pod副本数量
    • 一次性任务和定时任务
    • 无状态/有状态应用部署
    • 确保所有的node运行同一个pod
  3. service:
    • 定义一组pod的访问规则

搭建k8s集群

平台规划

  • 单master集群
  • 多master集群 - 负载均衡

安装软路由koolshare(SSR节点)

配置私有仓库(X)

kubeadm方式

Kubeadm 降低部署门槛,但屏蔽了很多细节,遇到问题很难排查。如果想更容易可控,推荐使用二进制包部署Kubernetes 集群,虽然手动部署麻烦点,期间可以学习很多工作原理,也利于后期维护

  1. 创建一个Master 节点 kubeadm init
  2. 将Node 节点加入到当前集群中$ kubeadm join <Master 节点的IP 和端口>

准备环境

角色 IP
master 192.168.1.11
node1 192.168.1.12
node2 192.168.1.13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 关闭防火墙(在生产环境中不推荐关闭防火墙,可以通过配置防火墙规则来允许 Kubernetes 所需的端口和服务)
systemctl stop firewalld
systemctl disable firewalld

# 关闭selinux(生产环境中应谨慎禁用 SELinux,应该根据需要进行策略配置)
# SELinux是一种强制访问控制机制,可能会阻止 Kubernetes 运行容器时访问某些系统资源。通过将其禁用,避免其对 Kubernetes 操作产生影响
sed -i 's/enforcing/disabled/' /etc/selinux/config # 永久
setenforce 0 # 临时

# 关闭swap(Kubernetes 要求禁用 swap,因为 Kubernetes 的调度和资源管理依赖于内存的固定分配,使用 swap 可能导致节点内存压力异常)
swapoff -a # 临时
sed -ri 's/.*swap.*/#&/' /etc/fstab # 永久

# 根据规划设置主机名
hostnamectl set-hostname <hostname>

# 在master添加hosts
cat >> /etc/hosts << EOF
192.168.44.146 k8smaster
192.168.44.145 k8snode1
192.168.44.144 k8snode2
EOF

# 将桥接的IPv4流量传递到iptables的链
# 在 Linux 中,桥接的流量默认不会通过 iptables 进行过滤。这意味着即使设置了 iptables 规则,桥接流量可能仍然会绕过它们,不符合网络安全的预期
# 这些配置项将会启用桥接网络流量通过 iptables 的处理,使得可以通过 iptables 设置适当的规则来控制这些流量
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv6.conf.all.disable_ipv6 = 1
EOF
sysctl --system # 生效

# 时间同步
# Kubernetes 集群中的节点需要保持时间同步,尤其是在使用证书、日志等需要精确时间戳的操作时。如果时间不一致,可能导致集群中出现各种问题
yum install ntpdate -y
ntpdate time.windows.com

所有节点安装containerd/kubeadm/kubelet

安装containerd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 安装containerd前置依赖
yum install -y yum-utils device-mapper-persistent-data lvm2

# 添加官方源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 安装containerd
yum install -y containerd.io

# 生成默认配置文件
containerd config default > /etc/containerd/config.toml
sed -i 's#registry.k8s.io/pause#registry.k8s.io/pause#g' /etc/containerd/config.toml
sed -i 's#SystemdCgroup = false#SystemdCgroup = true#g' /etc/containerd/config.toml

# 启动并设置开机自启
systemctl enable containerd && systemctl start containerd

# 验证 containerd 是否正常运行
crictl info

安装kubeadm,kubelet和kubectl(1.28)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 添加官方 Kubernetes 源
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key
EOF

# 安装指定版本
yum install -y kubelet-1.28.0 kubeadm-1.28.0 kubectl-1.28.0

# 确保kubelet使用systemd作为cgroup驱动
echo 'KUBELET_EXTRA_ARGS="--cgroup-driver=systemd"' > /etc/sysconfig/kubelet

# 启用kubelet
systemctl enable kubelet

部署Kubernetes Master

1
2
3
4
5
6
7
8
9
10
11
12
# 初始化Master节点(替换为实际IP)
kubeadm init \
--apiserver-advertise-address=192.168.44.146 \
--kubernetes-version v1.28.0 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16 \
--image-repository registry.aliyuncs.com/google_containers

# 配置kubectl
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

加入Kubernetes Node

在Node节点执行 kubeadm init 输出的加入命令(自动包含token及证书哈希):

1
2
kubeadm join 192.168.1.11:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>

默认token有效期为24小时,当过期之后,该token就不可用了。这时就需要重新创建token,操作如下:

1
kubeadm token create --print-join-command

Flannel网络插件安装(适用于小型集群)

1
2
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl get pods -n kube-system
  1. 测试kubernetes集群

在Kubernetes集群中创建一个pod,验证是否正常运行:

1
2
3
4
5
6
7
8
9
# 查看节点状态
kubectl get nodes

# 测试部署Nginx
kubectl create deployment nginx --image=nginx:alpine
kubectl expose deployment nginx --port=80 --type=NodePort

# 获取服务访问端口
kubectl get svc nginx

访问地址:http://<NodeIP>:<NodePort>

二进制方式

操作系统初始化

为etcd和apiserver自签证书

部署etcd集群

部署master组件

部署node组件

部署集群网络

Pod

Pod概述

Podkubernetes中可以创建和部署的最小也是最简的单位。Pod代表着集群中运行的进程。

Pod中封装着应用的容器(有的情况下是好几个容器),存储、独立的网络IP,管理容器如何运行的策略选项。Pod代表着部署的一个单位:kubernetes中应用的一个实例,可能由一个或者多个容器组合在一起共享资源。

Kubernetes集群中Pod有如下两种方式:

  • 一个Pod中运行一个容器。“每个Pod中一个容器”的模式是最常见的用法;在这种使用方式中,可以把Pod想象成单个容器的封装,Kubernetes管理的是Pod而不是直接管理容器。
  • 在一个Pod中同时运行多个容器。一个Pod也可以同时封装几个需要紧密耦合互相协作的容器,它们之间共享资源。这些在同一个Pod中的容器可以互相协作成为一个service单位——一个容器共享文件,另一个“sidecar”容器来更新这些文件。Pod将这些容器的存储资源作为一个实体来管理。

Pod中共享的环境包括Linuxnamespacecgroup和其他可能的隔绝环境,这一点跟Docker容器一致。在Pod的环境中,每个容器可能还有更小的子隔离环境。

Pod中的容器共享IP地址和端口号,它们之间可以通过localhost互相发现。它们之间可以通过进程间通信,例如SystemV信号或者POSIX共享内存。不同Pod之间的容器具有不同的IP地址,不能直接通过IPC通信。

Pod中的容器也有访问共享volume的权限,这些volume会被定义成pod的一部分并挂载到应用容器的文件系统中。

就像每个应用容器,pod被认为是临时(非持久的)实体。pod被创建后,被分配一个唯一的ID(UID),调度到节点上,并一致维持期望的状态直到被终结(根据重启策略)或者被删除。如果node死掉了,分配到了这个node上的pod,在经过一个超时时间后会被重新调度到其他node节点上。一个给定的pod(如UID定义的)不会被“重新调度”到新的节点上,而是被一个同样的pod取代,如果期望的话甚至可以是相同的名字,但是会有一个新的UID

Pod分类

自主式 Pod:pod 退出了,此类型的 Pod 不会被创建

控制器管理的 pod:在控制器的生命周期里,始终要维持 pod 的副本数目

Pod中如何管理多个容器

Pod中可以同时运行多个进程(作为容器运行)协同工作。同一个Pod中的容器会自动的分配到同一个node上。同一个Pod中的容器共享资源、网络环境和依赖,它们总是被同时调度。

注意在一个Pod中同时运行多个容器是一种比较高级的用法。只有当容器需要紧密配合协作的时候才考虑用这种模式。例如,有一个容器作为web服务器运行,需要用到共享的volume,有另一个“sidecar”容器来从远端获取资源更新这些文件

Pod中可以共享两种资源:网络和存储

网络:每个pod都会被分配一个唯一的IP地址。Pod中的所有容器共享网络空间,包括IP地址和端口。Pod内部的容器可以使用localhost互相通信。Pod中的容器与外界通信时,必须分配共享网络资源(例如使用宿主机的端口映射)。

存储:可以为一个Pod指定多个共享的VolumePod中的所有容器都可以访问共享的volumeVolume也可以用来持久化Pod中的存储资源,以防容器重启后文件丢失。

使用Pod

很少会直接在kubernetes中创建单个Pod。因为Pod的生命周期是短暂的,用后即焚的实体。当Pod被创建后(不论是由你直接创建还是被其它Controller),都会被Kubernetes调度到集群的Node上。直到Pod的进程终止、被删掉、因为缺少资源而被驱逐、或者Node故障之前这个Pod都会一直保持在那个Node上。

Pod不会自愈。如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除。同样的,如果Pod所在Node缺少资源或者Pod处于维护状态,Pod也会被驱逐。Kubernetes使用更高级的称为Controller的抽象层,来管理Pod实例。虽然可以直接使用Pod,但是在Kubernetes中通常是使用Controller来管理Pod的。

Controller可以创建和管理多个Pod,提供副本管理、滚动升级和集群级别的自愈能力。例如,如果一个Node故障,Controller就能自动将该节点上的Pod调度到其他健康的Node上。

注意:重启Pod中的容器跟重启Pod不是一回事。Pod只提供容器的运行环境并保持容器的运行状态,重启容器不会造成Pod重启。

Pod phase

  • 挂起(Pending):Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度 Pod 的时间和通过网络下载镜像的时间,这可能需要花点时间
  • 运行中(Running):该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或币启状态
  • 成功(Succeeded):Pod中的所有容器都被成功终止,并且不会再重启
  • 失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止
  • 未知(Unknown):因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败

Pod对象的生命周期

InitC

Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init容器
Init 容器与普通的容器非常像,除了如下两点:

  • Init容器总是运行到成功完成为止
  • 每个 Init 容器都必须在下一个 Init 容启动之前成功完成

如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而如果 Pod 对应的 restartPolicy为Never,它不会重新启动

因为 Init 容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:

  • 它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这
    些实用工具的
  • 它们可以包含使用工具和定制化代码来安装,但是不能出现在应用程序镜像中。例如,创建镜像没必要 FROM 另一个镜像,只需要在安装过程中使用类似 sed、 amk、 python 或 dig这样的工具。
  • 应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
  • Init 容器使用 Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用程序容器则不能。
  • 它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以Init 容器能够提供了一种简单的阻寒或延迟应用容器的启动的方法,直满足了一组先决条件。

特殊说明

  • 在 Pod 启动过程中,Init容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出
  • 如果由于运行时或失败退出,将导致容器启动失败,它会根据 Pod 的restartPolicy 指定的策略进行重试。然而,如果 Pod 的 restartPolicy 设置为 Always,Init 容器失败时会使用RestartPolicy策略
  • 在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready 状态。Init 容器的端口将不会在Service 中进行聚集。 正在初始化中的 Pod 处于 Pending 状态,但应该会将 Initializing 状态设置为 true
  • 如果 Pod 重启,所有 Init 容器必须重新执行
  • 对 Init 容器 spec 的修改被限制在容器 image 字段,修改其他字段都不会生效。更改 Init容器的 image 字段,等价于重启该 Pod
  • Init 容器具有应用容器的所有字段。除了 readinessProbe,因为 Init 容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态。这会在验证过程中强制执行
  • 在 Pod 中的每个 app 和 Init 容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误

initContainers 示例

编写yaml文件init-pod-demo.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@k8s-master ~]# vim manfests/init-pod-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp
image: busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: busybox
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
创建myapp-pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-master ~]# kubectl create -f manfests/init-pod-demo.yaml
pod/myapp-pod created
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 3s
[root@k8s-master ~]# kubectl describe pod myapp-pod
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m12s default-scheduler Successfully assigned default/myapp-pod to 192.168.3.27
Normal Pulling 2m11s kubelet, 192.168.3.27 Pulling image "busybox"
Normal Pulled 2m5s kubelet, 192.168.3.27 Successfully pulled image "busybox"
Normal Created 2m5s kubelet, 192.168.3.27 Created container init-myservice
Normal Started 2m5s kubelet, 192.168.3.27 Started container init-myservice

通过上面创建和查看状态,pod一直处于init阶段,因为还没有创建myservicemydb,里面的initContainers容器中命令还未探测成功

创建myservice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master ~]# vim manfests/init-myservice.yaml
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 80

[root@k8s-master ~]# kubectl create -f manfests/init-myservice.yaml
service/myservice created

[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:1/2 0 8m58s

myservice资源对象创建完成后,等待一会查看pod的状态,可以看到pod的状态中已经完成了一个init初始化。

创建mydb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@k8s-master ~]# vim manfests/init-mydb.yaml
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 3306
targetPort: 3306

[root@k8s-master ~]# kubectl create -f init-mydb.yaml
service/mydb created

[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 13m

[root@k8s-master ~]# kubectl describe pod myapp-pod
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 13m default-scheduler Successfully assigned default/myapp-pod to 192.168.3.27
Normal Pulling 13m kubelet, 192.168.3.27 Pulling image "busybox"
Normal Pulled 13m kubelet, 192.168.3.27 Successfully pulled image "busybox"
Normal Created 13m kubelet, 192.168.3.27 Created container init-myservice
Normal Started 13m kubelet, 192.168.3.27 Started container init-myservice
Normal Pulling 4m26s kubelet, 192.168.3.27 Pulling image "busybox"
Normal Pulled 4m19s kubelet, 192.168.3.27 Successfully pulled image "busybox"
Normal Created 4m19s kubelet, 192.168.3.27 Created container init-mydb
Normal Started 4m19s kubelet, 192.168.3.27 Started container init-mydb
Normal Pulling 43s kubelet, 192.168.3.27 Pulling image "busybox"
Normal Pulled 28s kubelet, 192.168.3.27 Successfully pulled image "busybox"
Normal Created 28s kubelet, 192.168.3.27 Created container myapp
Normal Started 27s kubelet, 192.168.3.27 Started container myapp

探针

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

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

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

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

探测方式

  • livenessProbe:指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其 重启策略的影响。如果容器不提供存活探针,则默认状态为Success

    设置exec探针示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    [root@k8s-master ~]# vim manfests/liveness-exec.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: liveness-exec-pod
    namespace: default
    labels:
    test: liveness-exec
    spec:
    containers:
    - name: liveness-exec-container
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 3600"]
    livenessProbe:
    exec:
    command: ["test","-e","/tmp/healthy"]
    initialDelaySeconds: 1
    periodSeconds: 3

    [root@k8s-master ~]# kubectl create -f manfests/liveness-exec.yaml #创建pod
    pod/liveness-exec-pod created
    [root@k8s-master ~]# kubectl get pods #查看pod
    NAME READY STATUS RESTARTS AGE
    liveness-exec-pod 1/1 Running 0 6s

    #等待一段时间后再次查看其状态
    [root@k8s-master ~]# kubectl get pods
    NAME READY STATUS RESTARTS AGE
    liveness-exec-pod 1/1 Running 2 2m46s

    上面的资源清单中定义了一个pod对象,基于busybox镜像启动一个运行“touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 3600"命令的容器,此命令在容器启动时创建了/tmp/healthy文件,并于60秒之后将其删除。存活性探针运行”test -e /tmp/healthy"命令检查/tmp/healthy文件的存在性,若文件存在则返回状态码0,表示成功通过测试。在60秒内使用“kubectl describe pods/liveness-exec-pod”查看其详细信息,其存活性探测不会出现错误。而超过60秒之后,再执行该命令查看详细信息,可以发现存活性探测出现了故障,并且还可通过“kubectl get pods"查看该pod的重启的相关信息

    设置HTTP探针示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    # httpGet定义的字段
    host <string>:请求的主机地址,默认为Pod IP,也可以在httpHeaders中使用“Host:”来定义。
    httpHeaders <[]Object>:自定义的请求报文首部。
    port <string>:请求的端口,必选字段。
    path <string>:请求的HTTP资源路径,即URL path。
    scheme <string>:建立连接使用的协议,仅可为HTTP或HTTPS,默认为HTTP。

    [root@k8s-master ~]# vim manfests/liveness-httpget.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: liveness-http
    namespace: default
    labels:
    test: liveness
    spec:
    containers:
    - name: liveness-http-demo
    image: nginx:1.12
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
    containerPort: 80
    lifecycle:
    postStart:
    exec:
    command: ["/bin/sh", "-c", "echo Healthz > /usr/share/nginx/html/healthz"]
    livenessProbe:
    httpGet:
    path: /healthz
    port: http
    scheme: HTTP
    [root@k8s-master ~]# kubectl create -f manfests/liveness-httpget.yaml #创建pod
    pod/liveness-http created
    [root@k8s-master ~]# kubectl get pods #查看pod
    NAME READY STATUS RESTARTS AGE
    liveness-http 1/1 Running 0 7s

    [root@k8s-master ~]# kubectl describe pods/liveness-http #查看liveness-http详细信息
    ......
    Containers:
    liveness-http-demo:
    ......
    Port: 80/TCP
    Host Port: 0/TCP
    State: Running
    Started: Mon, 09 Sep 2019 15:43:29 +0800
    Ready: True
    Restart Count: 0
    ......

    [root@k8s-master ~]# kubectl exec pods/liveness-http -it -- /bin/sh #进入到上面创建的pod中
    # rm -rf /usr/share/nginx/html/healthz #删除healthz测试页面
    #

    [root@k8s-master ~]# kubectl get pods
    NAME READY STATUS RESTARTS AGE
    liveness-http 1/1 Running 1 10m

    [root@k8s-master ~]# kubectl describe pods/liveness-http
    ......
    Containers:
    liveness-http-demo:
    ......
    Port: 80/TCP
    Host Port: 0/TCP
    State: Running
    Started: Mon, 09 Sep 2019 15:53:04 +0800
    Last State: Terminated
    Reason: Completed
    Exit Code: 0
    Started: Mon, 09 Sep 2019 15:43:29 +0800
    Finished: Mon, 09 Sep 2019 15:53:03 +0800
    Ready: True
    Restart Count: 1
    ......

    上面清单中定义的httpGet测试中,通过lifecycle中的postStart hook创建了一个专用于httpGet测试的页面文件healthz,请求的资源路径为"/healthz",地址默认为Pod IP,端口使用了容器中顶一个端口名称http,这也是明确了为容器指明要暴露的端口的用途之一。并查看健康状态检测相关的信息,健康状态检测正常时,容器也将正常运行。下面通过“kubectl exec”命令进入容器删除由postStart hook创建的测试页面healthz。再次查看容器状态

    通过测试可以看出,当发起http请求失败后,容器将被杀掉后进行了重新构建

  • readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为Success

    设置HTTP探针示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #终端1:
    [root@k8s-master ~]# vim manfests/readiness-httpget.yaml #编辑readiness-httpget测试pod的yaml文件
    apiVersion: v1
    kind: Pod
    metadata:
    name: readiness-http
    namespace: default
    labels:
    test: readiness-http
    spec:
    containers:
    - name: readiness-http-demo
    image: nginx:1.12
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
    containerPort: 80
    readinessProbe:
    httpGet:
    path: /index.html
    port: http
    scheme: HTTP
    [root@k8s-master ~]# kubectl create -f manfests/readiness-httpget.yaml #创建pod
    pod/readiness-http created
    [root@k8s-master ~]# kubectl get pods 查看pod状态
    NAME READY STATUS RESTARTS AGE
    liveness-tcp-pod 1/1 Running 1 7d18h
    readiness-http 1/1 Running 0 7s


    #新打开一个终端2进入到容器里面
    [root@k8s-master ~]# kubectl exec pods/readiness-http -it -- /bin/sh #进入上面创建的pod
    # rm -f /usr/share/nginx/html/index.html #删除nginx的主页面文件
    # ls /usr/share/nginx/html
    50x.html
    #


    #回到终端1上面查看pod状态
    [root@k8s-master ~]# kubectl get pods #查看pod状态
    NAME READY STATUS RESTARTS AGE
    liveness-tcp-pod 1/1 Running 1 7d18h
    readiness-http 0/1 Running 0 2m44s

    通过上面测试可以看出,当我们删除了nginx主页文件后,readinessProbe发起的测试就会失败,此时我们再查看pod的状态会发现并不会将pod删除重新启动,只是在READY字段可以看出,当前的Pod处于未就绪状态

启动、退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh","-c","echo Hello from the postStart handler > /usr/share/message"]
postStop:
exec:
command: ["/bin/sh","-c","echo Stop from the postStop handler > /usr/share/message"]

Pod控制器类型

ReplicationController & ReplicaSet & Deployment

  • ReplicationController 用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的Pod 来替代,而如果异常多出来的容器也会自动回收。在新版本的 Kubernetes中建议使用ReplicaSet来取代 ReplicationController
  • ReplicaSet 跟 ReplicationController 没有本质的不同,只是名字不一样,并且Replicaset 支持集合式的selector(标签)
  • 虽然 ReplicaSet 可以独立使用,但一般还是建议使用 Deployment 来自动管理ReplicaSet
  • Deployment本身无法创建pod,通过创建ReplicaSet来获得创建pod的能力
  • Deployment为 Pod 和 Replicaset 提供了一个声明式定义(declarative) 方法,用来替代以前的Replicationcontroller 来方便的管理应用。典型的应用场景包括;
    • 定义 Deployment 来创建 Pod 和 Replicaset
    • 滚动升级和回滚应用
    • 扩容和缩容
    • 暂停和继续 Deployment

ReplicaSet

核心字段

spec字段一般嵌套使用以下几个属性字段:

1
2
3
4
replicas	<integer>:指定期望的Pod对象副本数量
selector <Object>:当前控制器匹配Pod对象副本的标签选择器,支持matchLabels和matchExpressions两种匹配机制
template <Object>:用于定义Pod时的Pod资源信息
minReadySeconds <integer>:用于定义Pod启动后多长时间为可用状态,默认为0秒
ReplicaSet示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#(1)命令行查看ReplicaSet清单定义规则
[root@k8s-master ~]# kubectl explain rs
[root@k8s-master ~]# kubectl explain rs.spec
[root@k8s-master ~]# kubectl explain rs.spec.template


#(2)创建ReplicaSet示例
[root@k8s-master ~]# vim manfests/rs-demo.yaml
apiVersion: apps/v1 #api版本定义
kind: ReplicaSet #定义资源类型为ReplicaSet
metadata: #元数据定义
name: myapp
namespace: default
spec: #ReplicaSet的规格定义
replicas: 2 #定义副本数量为2个
selector: #标签选择器,定义匹配Pod的标签
matchLabels:
app: myapp
release: canary
template: #Pod的模板定义
metadata: #Pod的元数据定义
name: myapp-pod #自定义Pod的名称
labels: #定义Pod的标签,需要和上面的标签选择器内匹配规则中定义的标签一致,可以多出其他标签
app: myapp
release: canary
spec: #Pod的规格定义
containers: #容器定义
- name: myapp-containers #容器名称
image: ikubernetes/myapp:v1 #容器镜像
imagePullPolicy: IfNotPresent #拉取镜像的规则
ports: #暴露端口
- name: http #端口名称
containerPort: 80


#(3)创建ReplicaSet定义的Pod
[root@k8s-master ~]# kubectl apply -f manfests/rs-demo.yaml
replicaset.apps/myapp created
[root@k8s-master ~]# kubectl get rs #查看创建的ReplicaSet控制器
NAME DESIRED CURRENT READY AGE
myapp 4 4 4 3m23s
[root@k8s-master ~]# kubectl get pods #通过查看pod可以看出pod命令是规则是前面是replicaset控制器的名称加随机生成的字符串
NAME READY STATUS RESTARTS AGE
myapp-bln4v 1/1 Running 0 6s
myapp-bxpzt 1/1 Running 0 6s


#(4)修改Pod的副本数量
[root@k8s-master ~]# kubectl edit rs myapp
replicas: 4
[root@k8s-master ~]# kubectl get rs -o wide
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
myapp 4 4 4 2m50s myapp-containers ikubernetes/myapp:v2 app=myapp,release=canary
[root@k8s-master ~]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
myapp-8hkcr 1/1 Running 0 2m2s app=myapp,release=canary
myapp-bln4v 1/1 Running 0 3m40s app=myapp,release=canary
myapp-bxpzt 1/1 Running 0 3m40s app=myapp,release=canary
myapp-ql2wk 1/1 Running 0 2m2s app=myapp,release=canary
更新ReplicaSet控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@k8s-master ~]# vim manfests/rs-demo.yaml
spec: #Pod的规格定义
containers: #容器定义
- name: myapp-containers #容器名称
image: ikubernetes/myapp:v2 #容器镜像
imagePullPolicy: IfNotPresent #拉取镜像的规则
ports: #暴露端口
- name: http #端口名称
containerPort: 80
[root@k8s-master ~]# kubectl apply -f manfests/rs-demo.yaml #执行apply让其重载
[root@k8s-master ~]# kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name Image
myapp-bln4v ikubernetes/myapp:v1
myapp-bxpzt ikubernetes/myapp:v1

#说明:这里虽然重载了,但是已有的pod所使用的镜像仍然是v1版本的,只是新建pod时才会使用v2版本,这里测试先手动删除已有的pod。
[root@k8s-master ~]# kubectl delete pods -l app=myapp #删除标签app=myapp的pod资源
pod "myapp-bln4v" deleted
pod "myapp-bxpzt" deleted
[root@k8s-master ~]# kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image #再次查看通过ReplicaSet新建的pod资源对象。镜像已使用v2版本
Name Image
myapp-mdn8j ikubernetes/myapp:v2
myapp-v5bgr ikubernetes/myapp:v2
扩容和缩容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@k8s-master ~]# kubectl get rs    #查看ReplicaSet
NAME DESIRED CURRENT READY AGE
myapp 2 2 2 154m
[root@k8s-master ~]# kubectl get pods #查看Pod
NAME READY STATUS RESTARTS AGE
myapp-mdn8j 1/1 Running 0 5m26s
myapp-v5bgr 1/1 Running 0 5m26s

#扩容
[root@k8s-master ~]# kubectl scale replicasets myapp --replicas=5 #将上面的Deployments控制器myapp的Pod副本数量提升为5个
replicaset.extensions/myapp scaled
[root@k8s-master ~]# kubectl get rs #查看ReplicaSet
NAME DESIRED CURRENT READY AGE
myapp 5 5 5 156m
[root@k8s-master ~]# kubectl get pods #查看Pod
NAME READY STATUS RESTARTS AGE
myapp-lrrp8 1/1 Running 0 8s
myapp-mbqf8 1/1 Running 0 8s
myapp-mdn8j 1/1 Running 0 6m48s
myapp-ttmf5 1/1 Running 0 8s
myapp-v5bgr 1/1 Running 0 6m48s

#收缩
[root@k8s-master ~]# kubectl scale replicasets myapp --replicas=3
replicaset.extensions/myapp scaled
[root@k8s-master ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
myapp 3 3 3 159m
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-mdn8j 1/1 Running 0 10m
myapp-ttmf5 1/1 Running 0 3m48s
myapp-v5bgr 1/1 Running 0 10m
删除ReplicaSet控制器资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
myapp 3 3 3 162m
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-mdn8j 1/1 Running 0 12m
myapp-ttmf5 1/1 Running 0 6m18s
myapp-v5bgr 1/1 Running 0 12m
[root@k8s-master ~]# kubectl delete replicasets myapp --cascade=false
replicaset.extensions "myapp" deleted
[root@k8s-master ~]# kubectl get rs
No resources found.
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-mdn8j 1/1 Running 0 13m
myapp-ttmf5 1/1 Running 0 7m
myapp-v5bgr 1/1 Running 0 13m

使用Kubectl delete命令删除ReplicaSet对象时默认会一并删除其管控的各Pod对象,有时,考虑到这些Pod资源未必由其创建,或者即便由其创建也并非自身的组成部分,这时候可以添加“--cascade=false”选项,取消级联关系

Deployment

创建Deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#(1)命令行查看ReplicaSet清单定义规则
[root@k8s-master ~]# kubectl explain deployment
[root@k8s-master ~]# kubectl explain deployment.spec
[root@k8s-master ~]# kubectl explain deployment.spec.template


#(2)创建Deployment示例
[root@k8s-master ~]# vim manfests/deploy-demo.yaml
apiVersion: apps/v1 #api版本定义
kind: Deployment #定义资源类型为Deploymant
metadata: #元数据定义
name: deploy-demo #deployment控制器名称
namespace: default #名称空间
spec: #deployment控制器的规格定义
replicas: 2 #定义副本数量为2个
selector: #标签选择器,定义匹配Pod的标签
matchLabels:
app: deploy-app
release: canary
template: #Pod的模板定义
metadata: #Pod的元数据定义
labels: #定义Pod的标签,需要和上面的标签选择器内匹配规则中定义的标签一致,可以多出其他标签
app: deploy-app
release: canary
spec: #Pod的规格定义
containers: #容器定义
- name: myapp #容器名称
image: ikubernetes/myapp:v1 #容器镜像
ports: #暴露端口
- name: http #端口名称
containerPort: 80



#(3)创建Deployment对象
[root@k8s-master ~]# kubectl apply -f manfests/deploy-demo.yaml
deployment.apps/deploy-demo created



#(4)查看资源对象
[root@k8s-master ~]# kubectl get deployment #查看Deployment资源对象
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-demo 2/2 2 2 10s
[root@k8s-master ~]# kubectl get replicaset #查看ReplicaSet资源对象
NAME DESIRED CURRENT READY AGE
deploy-demo-78c84d4449 2 2 2 20s
[root@k8s-master ~]# kubectl get pods #查看Pod资源对象
NAME READY STATUS RESTARTS AGE
deploy-demo-78c84d4449-22btc 1/1 Running 0 23s
deploy-demo-78c84d4449-5fn2k 1/1 Running 0 23s
---
说明:
通过查看资源对象可以看出,Deployment会自动创建相关的ReplicaSet控制器资源,并以"[DEPLOYMENT-name]-[POD-TEMPLATE-HASH-VALUE]"格式为其命名,其中的hash值由Deployment自动生成。而Pod名则是以ReplicaSet控制器的名称为前缀,后跟5位随机字符。
更新策略

Deployment控制器支持两种更新策略:滚动更新(rollingUpdate)和重建创新(Recreate),默认为滚动更新

  • 滚动更新(rollingUpdate):即在删除一部分旧版本Pod资源的同时,补充创建一部分新版本的Pod对象进行应用升级,其优势是升级期间,容器中应用提供的服务不会中断,但更新期间,不同客户端得到的相应内容可能会来自不同版本的应用

  • 重新创建(Recreate):即首先删除现有的Pod对象,而后由控制器基于新模板重行创建出新版本的资源对象

滚动更新时,应用还要确保可用的Pod对象数量不低于某阀值以确保可以持续处理客户端的服务请求,变动的方式和Pod对象的数量范围将通过kubectl explain deployment.spec.strategy.rollingUpdate.maxSurgekubectl explain deployment.spec.strategy.rollingUpdate.maxUnavailable两个属性同时进行定义。其功能如下:

  • maxSurge:指定升级期间存在的总Pod对象数量最多可超出期望值的个数,其值可以是0或正整数,也可以是一个期望值的百分比;例如,如果期望值为3,当前的属性值为1,则表示Pod对象的总数不能超过4个。

  • maxUnavailable:升级期间正常可用的Pod副本数(包括新旧版本)最多不能低于期望值的个数,其值可以是0或正整数,也可以是期望值的百分比;默认值为1,该值意味着如果期望值是3,则升级期间至少要有两个Pod对象处于正常提供服务的状态。

maxSurgemaxUnavailable属性的值不可同时为0,否则Pod对象的副本数量在符合用户期望的数量后无法做出合理变动以进行滚动更新操作。

Deployment控制器可以保留其更新历史中的旧ReplicaSet对象版本,所保存的历史版本数量由kubectl explain deployment.spec.revisionHistoryLimit参数指定。只有保存于revision历史中的ReplicaSet版本可用于回滚。

注:为了保存版本升级的历史,需要在创建Deployment对象时于命令中使用“--record”选项。

首先通过set image命令将上面创建的Deployment对象的镜像版本改为v2版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#打开1个终端进行升级
[root@k8s-master ~]# kubectl set image deployment/deploy-demo myapp=ikubernetes/myapp:v2
deployment.extensions/deploy-demo image updated

#同时打开终端2进行查看pod资源对象升级过程
[root@k8s-master ~]# kubectl get pods -l app=deploy-app -w
NAME READY STATUS RESTARTS AGE
deploy-demo-78c84d4449-2rvxr 1/1 Running 0 33s
deploy-demo-78c84d4449-nd7rr 1/1 Running 0 33s
deploy-demo-7c66dbf45b-7k4xz 0/1 Pending 0 0s
deploy-demo-7c66dbf45b-7k4xz 0/1 Pending 0 0s
deploy-demo-7c66dbf45b-7k4xz 0/1 ContainerCreating 0 0s
deploy-demo-7c66dbf45b-7k4xz 1/1 Running 0 2s
deploy-demo-78c84d4449-2rvxr 1/1 Terminating 0 49s
deploy-demo-7c66dbf45b-r88qr 0/1 Pending 0 0s
deploy-demo-7c66dbf45b-r88qr 0/1 Pending 0 0s
deploy-demo-7c66dbf45b-r88qr 0/1 ContainerCreating 0 0s
deploy-demo-7c66dbf45b-r88qr 1/1 Running 0 1s
deploy-demo-78c84d4449-2rvxr 0/1 Terminating 0 50s
deploy-demo-78c84d4449-nd7rr 1/1 Terminating 0 51s
deploy-demo-78c84d4449-nd7rr 0/1 Terminating 0 51s
deploy-demo-78c84d4449-nd7rr 0/1 Terminating 0 57s
deploy-demo-78c84d4449-nd7rr 0/1 Terminating 0 57s
deploy-demo-78c84d4449-2rvxr 0/1 Terminating 0 60s
deploy-demo-78c84d4449-2rvxr 0/1 Terminating 0 60s


#同时打开终端3进行查看pod资源对象变更过程
[root@k8s-master ~]# kubectl get deployment deploy-demo -w
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-demo 2/2 2 2 37s
deploy-demo 2/2 2 2 47s
deploy-demo 2/2 2 2 47s
deploy-demo 2/2 0 2 47s
deploy-demo 2/2 1 2 47s
deploy-demo 3/2 1 3 49s
deploy-demo 2/2 1 2 49s
deploy-demo 2/2 2 2 49s
deploy-demo 3/2 2 3 50s
deploy-demo 2/2 2 2 51s


# 升级完成再次查看rs的情况,以下可以看到原的rs作为备份,而现在启动的是新的rs
[root@k8s-master ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
deploy-demo-78c84d4449 0 0 0 4m41s
deploy-demo-7c66dbf45b 2 2 2 3m54s

Deployment扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#1、使用kubectl scale命令扩容
[root@k8s-master ~]# kubectl scale deployment deploy-demo --replicas=3
deployment.extensions/deploy-demo scaled
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
deploy-demo-7c66dbf45b-7k4xz 1/1 Running 0 10m
deploy-demo-7c66dbf45b-gq2tw 1/1 Running 0 3s
deploy-demo-7c66dbf45b-r88qr 1/1 Running 0 10m

#2、使用直接修改配置清单方式进行扩容
[root@k8s-master ~]# vim manfests/deploy-demo.yaml
spec: #deployment控制器的规格定义
replicas: 4 #定义副本数量为2个
[root@k8s-master ~]# kubectl apply -f manfests/deploy-demo.yaml
deployment.apps/deploy-demo configured
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
deploy-demo-78c84d4449-6rmnm 1/1 Running 0 61s
deploy-demo-78c84d4449-9xfp9 1/1 Running 0 58s
deploy-demo-78c84d4449-c2m6h 1/1 Running 0 61s
deploy-demo-78c84d4449-sfxps 1/1 Running 0 57s

#3、使用kubectl patch打补丁的方式进行扩容
[root@k8s-master ~]# kubectl patch deployment deploy-demo -p '{"spec":{"replicas":5}}'
deployment.extensions/deploy-demo patched
[root@k8s-master ~]#
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
deploy-demo-78c84d4449-6rmnm 1/1 Running 0 3m44s
deploy-demo-78c84d4449-9xfp9 1/1 Running 0 3m41s
deploy-demo-78c84d4449-c2m6h 1/1 Running 0 3m44s
deploy-demo-78c84d4449-sfxps 1/1 Running 0 3m40s
deploy-demo-78c84d4449-t7jxb 1/1 Running 0 3s

先添加后删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 添加其总数多余期望值一个
[root@k8s-master ~]# kubectl patch deployment deploy-demo -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
deployment.extensions/deploy-demo patched

# 启动更新过程,在修改相应容器的镜像版本后立即暂停更新进度。
[root@k8s-master ~]# kubectl set image deployment/deploy-demo myapp=ikubernetes/myapp:v3 && kubectl rollout pause deployment deploy-demo
deployment.extensions/deploy-demo image updated
deployment.extensions/deploy-demo paused


#查看
[root@k8s-master ~]# kubectl get deployment #查看deployment资源对象
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-demo 6/5 1 6 37m
[root@k8s-master ~]# kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image #查看pod资源对象的name和image
Name Image
deploy-demo-6bf8dbdc9f-fjnzn ikubernetes/myapp:v3
deploy-demo-78c84d4449-6rmnm ikubernetes/myapp:v1
deploy-demo-78c84d4449-9xfp9 ikubernetes/myapp:v1
deploy-demo-78c84d4449-c2m6h ikubernetes/myapp:v1
deploy-demo-78c84d4449-sfxps ikubernetes/myapp:v1
deploy-demo-78c84d4449-t7jxb ikubernetes/myapp:v1
[root@k8s-master ~]# kubectl rollout status deployment/deploy-demo #查看更新情况
Waiting for deployment "deploy-demo" rollout to finish: 1 out of 5 new replicas have been updated...
---
#通过上面查看可以看出,当前的pod数量为6个,因为此前我们定义的期望值为5个,这里多出了一个,且这个镜像版本为v3版本。



#全部更新
[root@k8s-master ~]# kubectl rollout resume deployment deploy-demo
deployment.extensions/deploy-demo resumed
#再次查看
[root@k8s-master ~]# kubectl get deployment #查看deployment资源对象
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-demo 5/5 5 5 43m
[root@k8s-master ~]# kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image #查看pod资源对象的name和image
Name Image
deploy-demo-6bf8dbdc9f-2z6gt ikubernetes/myapp:v3
deploy-demo-6bf8dbdc9f-f79q2 ikubernetes/myapp:v3
deploy-demo-6bf8dbdc9f-fjnzn ikubernetes/myapp:v3
deploy-demo-6bf8dbdc9f-pjf4z ikubernetes/myapp:v3
deploy-demo-6bf8dbdc9f-x7fnk ikubernetes/myapp:v3
[root@k8s-master ~]# kubectl rollout status deployment/deploy-demo #查看更新情况
Waiting for deployment "deploy-demo" rollout to finish: 1 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 1 out of 5 new replicas have been updated...
Waiting for deployment spec update to be observed...
Waiting for deployment spec update to be observed...
Waiting for deployment "deploy-demo" rollout to finish: 1 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 1 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 2 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 2 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 2 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "deploy-demo" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "deploy-demo" rollout to finish: 1 old replicas are pending termination...
deployment "deploy-demo" successfully rolled out
回滚Deployment控制器下的应用发布

若因各种原因导致滚动更新无法正常进行,如镜像文件获取失败,等等;则应该将应用回滚到之前的版本,或者回滚到指定的历史记录中的版本。则通过kubectl rollout undo命令完成。如果回滚到指定版本则需要添加--to-revision选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 回到上一个版本
[root@k8s-master ~]# kubectl rollout undo deployment/deploy-demo
deployment.extensions/deploy-demo rolled back
[root@k8s-master ~]# kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name Image
deploy-demo-78c84d4449-2xspz ikubernetes/myapp:v1
deploy-demo-78c84d4449-f8p46 ikubernetes/myapp:v1
deploy-demo-78c84d4449-mnmvc ikubernetes/myapp:v1
deploy-demo-78c84d4449-tsl7r ikubernetes/myapp:v1
deploy-demo-78c84d4449-xdt8j ikubernetes/myapp:v1

# 回到指定版本
#通过该命令查看更新历史记录
[root@k8s-master ~]# kubectl rollout history deployment/deploy-demo
deployment.extensions/deploy-demo
REVISION CHANGE-CAUSE
2 <none>
4 <none>
5 <none>

#回滚到版本2
[root@k8s-master ~]# kubectl rollout undo deployment/deploy-demo --to-revision=2
deployment.extensions/deploy-demo rolled back
[root@k8s-master ~]# kubectl get pods -o custom-columns=Name:metadata.name,Image:spec.containers[0].image
Name Image
deploy-demo-7c66dbf45b-42nj4 ikubernetes/myapp:v2
deploy-demo-7c66dbf45b-8zhf5 ikubernetes/myapp:v2
deploy-demo-7c66dbf45b-bxw7x ikubernetes/myapp:v2
deploy-demo-7c66dbf45b-gmq8x ikubernetes/myapp:v2
deploy-demo-7c66dbf45b-mrfdb ikubernetes/myapp:v2

HPA(HorizontalPodAutoScale)

应用的资源使用率通常都有高峰和低谷的时,如何削峰填谷,提高集群的整体资源利用率,让service中的个数自动调整呢?这就有赖于Horizontal Pod Autoscaling了,顾名思义,使Pod水平自动缩放

一个简单的说明,当CPU超过某一值的时候,HPA会创建Pod(有最大数量和最小数量),当CPU降下来的时候便不再创建Pod

StatefullSet

StatefulSet 是为了解决有状态服务的问题(对应 Deployments和ReplicaSets 是为无状态服务而设计),其应用场景包括:

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  • 稳定的网络标志,即Pod重新调度后其PodName和HostName 不变,基于 Headless Service(即没有ClusterIP的Service)来实现
  • 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行(即从0到N-1,在下一个Pod 运行之前所有之前的Pod 必须都是 Running和 Ready 状态),基于 init containers来实现
  • 有序收缩,有序删除(即从N-1到0)

DaemonSet

DaemonSet确保全部(或者一些)Node上运行一个Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些Pod也会被回收。删除 DaemonSet 将会删除它创建的所有Pod
使用 DaemonSet 的一些典型用法:

  • 运行集群存储 daemon,例如在每个Node上运行glusterd、ceph
  • 在每个 Node 上运行日志收集 daemon,例如fluentd、logstash
  • 在每个 Node 上运行监控daemon,例如Prometheus Node Exporter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: apps/v1
kind: DeamonSet
metadata:
name: deamonset-example
labels:
app: deamonset
spec:
selector:
matchLabels:
name: deamonset-example
template:
metadata:
labels:
name: deamonset-example
spec:
containers:
- name: deamonset-example
image: myapp:v1

Job,CronJob

Job负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束(有纠错能力),可以部署对应的脚本

特殊说明

  • spec.template格式同Pod
  • RestartPolicy仅支持Never或onFailure
  • 单个Pod时,默认Pod成功运行后Job即结束
  • .spec.completions 标志ob结束需要成功运行的Pod个数,默认为1
  • .spec.parallelism 标志并行运行的Pod的个数,默认为1
  • spec.activepeadlineseconds 标志失败Pod的重试最大时间,超过这个时间不会继续重试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
metadata:
name: pi
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum-bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never

Cron Job 管理基于时间的Job,即:

  • 在给定时间点只运行一次

  • 周期性地在给定时间点运行

  • 在给定的时间点调度Job运行;创建周期性运行的Job,如:数据库备份、发送邮件等

    • .spec.schedule:调度,必需字段,指定任务运行周期,格式同Cron
    • .spec.jobTemplate:Job 模板,必需字段,指定需要运行的任务,格式同Job
    • .spec.startingDeadlineSeconds:启动Job 的期限(秒级别),该字段是可选的。如果因为任何原因而错过了被调度的时间,那么错过执行时间的Job 将被认为是失败的。如果没有指定,则没有期限
    • .spec.concurrencyPolicy:并发策略,该字段也是可选的。它指定了如何处理被 CronJob 创建的Job 的并发执行。只允许指定下面策略中的一种:
      • Allow(默认):允许并发运行Job
      • Forbid :禁止并发运行,如果前一个还没有完成,则直接跳过下一个
      • Replace:取消当前正在运行的Job,用一个新的来替换
      • 注意,当前策略只能应用于同一个 CronJob 创建的Job。如果存在多个CronJob,它们创建的Job 之间总是允许并发运行
    • .spec.suspend:挂起,该字段也是可选的。如果设置为true,后续所有执行都会被挂起。它对已经开始执行的Job 不起作用。默认值为 false 。
    • .spec.successfulJobsHistoryLimit 和,spec.failedJobsHistoryLimit :历史限制,是可选的字段。它们指定了可以保留多少完成和失败的Job。默认情况下,它们分别设置为 3和1。设置限制的值为 0。相关类型的 Job 完成后将不会被保留。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure

容器运行报错的思路

1
2
3
4
kubectl get pod
kubectl describe pod <pod名称>
kubectl logs <pod名称> -c <容器名称>
# kubectl get pod -o wide

网络通讯模式

Kubernetes的网络模型假定了所有Pod都在一个可以直接连通的扁平的网络空间中,这在GCE(Google Compute Engine)里面是现成的网络模型,Kubernetes假定这个网络已经存在。而在私有云里搭建Kubernetes集群,就不能假定这个网络已经存在了。我们需要自己实现这个网络假设,将不同节点上的Docker容器之间的互相访问先打通,然后运行Kubernetes

  • 同一个Pod内的多个容器之间:lo
  • 各Pod之间的通讯:Overlay Network
  • Pod与Service之间的通讯:各节点的Iptables规则

Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。而且它还能在这些IP地址之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内

ETCD之Flannel:存储管理Flannel可分配的IP 地址段资源;监控ETCD中每个Pod的实际地址,并在内存中建立维护Pod节点路由表

同一个Pod 内部通讯:同一个Pod共享同一个网络命名空间,共享同一个Linux协议栈
Pod1至Pod2

  • Pod1与Pod2不在同一台主机,Pod的地址是与docker0在同一个网段的,但docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以互相访问

  • Pod1与Pod2在同一台机器,由Docker0网桥直接转发请求至Pod2,不需要经过Flannel

Pod至Service的网络:目前基于性能考虑,全部为iptables维护和转发

Pod 到外网:Pod 向外网发送请求,查找路由表,转发数据包到宿主机的网卡,宿主网卡完成路由选择后,iptables执行Masquerade,把源IP更改为宿主网卡的IP,然后向外网服务器发送请求

外网访问 Pod:Service

资源清单

集群资源分类

名称空间级别;集群级别;元数据型

类型 名称
工作负载(Workload) Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、Cronjob
负载均衡(Discovery &LB) Service、Ingress
配置和存储(Config&Storage) Volume、CSI(容器存储接口)、ConfigMap、Secret、DownwardAPI
特殊类型的存储卷 ConfigMap、Secret、DownwardAPI
集群(Cluster) Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding
元数据(metadata) HPA、PodTemplate、LimitRange

获取对象的JSON格式的配置清单可以通过”kubectl get TYPE/NAME -o yaml”命令来获取 kubectl get pod nginx-67685f79b5-8rjk7 -o yaml

创建资源的方法

  • apiserver仅接受JSON格式的资源定义

  • yaml格式提供资源配置清单,apiserver可自动将其转为json格式,而后再提交

大部分资源的配置清单由以下5个字段组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: 指明api资源属于哪个群组和版本,同一个组可以有多个版本 group/version
# kubectl api-versions 命令可以获取

kind: 资源类别,标记创建的资源类型,k8s主要支持以下资源类别
Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、Cronjob

metadata: 用于描述对象的属性信息,主要提供以下字段:
name: 指定当前对象的名称,其所属的名称空间的同一类型中必须唯一
namespace: 指定当前对象隶属的名称空间,默认值为default
labels: 设定用于标识当前对象的标签,键值数据,常被用作挑选条件
annotations: 非标识型键值数据,用来作为挑选条件,用于labels的补充

spec: 用于描述所期望的对象应该具有的状态(disired state),资源对象中最重要的字段。

status: 用于记录对象在系统上的当前状态(current state),本字段由kubernetes自行维护

kubernetes存在内嵌的格式说明,定义资源配置清单时,可以使用kubectl explain命令进行查看,如查看Pod这个资源的定义:

kubectl explain pods

如果需要了解某一级字段表示的对象之下的二级对象字段时,只需要指定其二级字段的对象名称即可,三级和四级字段对象等的查看方式依次类推。例如查看Pod资源的Spec对象支持嵌套使用的二级字段:

kubectl explain pods.spec

spec常用字段说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
spec
containers <[]Object> -required- # 必选参数
name <string> -required- # 指定容器名称,不可更新
image <string> -required- # 指定镜像
imagePullPolicy <string> # 指定镜像拉取方式
# Always: 始终从registory拉取镜像。如果镜像标签为latest,则默认值为Always
# Never: 仅使用本地镜像
# IfNotPresent: 本地不存在镜像时才去registory拉取。默认值
env <[]Object> # 指定环境变量,使用 $(var) 引用,参考: configmap中模板
command <[]string> # 以数组方式指定容器运行指令,替代docker的ENTRYPOINT指令
args <[]string> # 以数组方式指定容器运行参数,替代docker的CMD指令
ports <[]Object> # 指定容器暴露的端口
containerPort <integer> -required- # 容器的监听端口
name <string> # 为端口取名,该名称可以在service种被引用
protocol <string> # 指定协议,默认TCP
hostIP <string> # 绑定到宿主机的某个IP
hostPort <integer> # 绑定到宿主机的端口
readinessProbe <Object> # 就绪性探测,确认就绪后提供服务
initialDelaySeconds <integer> # 容器启动后到开始就绪性探测中间的等待秒数
periodSeconds <integer> # 两次探测的间隔多少秒,默认值为10
successThreshold <integer> # 连续多少次检测成功认为容器正常,默认值为1。不支持修改
failureThreshold <integer> # 连续多少次检测成功认为容器异常,默认值为3
timeoutSeconds <integer> # 探测请求超时时间
exec <Object> # 通过执行特定命令来探测容器健康状态
command <[]string> # 执行命令,返回值为0表示健康,不自持shell模式
tcpSocket <Object> # 检测TCP套接字
host <string> # 指定检测地址,默认pod的IP
port <string> -required-# 指定检测端口
httpGet <Object> # 以HTTP请求方式检测
host <string> # 指定检测地址,默认pod的IP
httpHeaders <[]Object> # 设置请求头
path <string> # 设置请求的location
port <string> -required-# 指定检测端口
scheme <string> # 指定协议,默认HTTP
livenessProbe <Object> # 存活性探测,确认pod是否具备对外服务的能力
# 该对象中字段和readinessProbe一致
lifecycle <Object> # 生命周期
postStart <Object> # pod启动后钩子,执行指令或者检测失败则退出容器或者重启容器
exec <Object> # 执行指令,参考readinessProbe.exec
httpGet <Object> # 执行HTTP,参考readinessProbe.httpGet
tcpSocket <Object> # 检测TCP套接字,参考readinessProbe.tcpSocket
preStop <Object> # pod停止前钩子,停止前执行清理工作
# 该对象中字段和postStart一致
hostname <string> # 指定pod主机名
nodeName <string> # 调度到指定的node节点
nodeSelector <map[string]string> # 指定预选的node节点
hostIPC <boolean> # 使用宿主机的IPC名称空间,默认false
hostNetwork <boolean> # 使用宿主机的网络名称空间,默认false
serviceAccountName <string> # Pod运行时的服务账号
imagePullSecrets <[]Object> # 当拉取私密仓库镜像时,需要指定的密码密钥信息
name <string> # secrets 对象名

配置清单模式创建Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@k8s-master ~]# mkdir manfests 
[root@k8s-master ~]# cd manfests/
[root@k8s-master manfests]# vim pod-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
- name: busybox
image: busybox:latest
command:
- "/bin/sh"
- "-c"
- "sleep 3600"

[root@k8s-master manfests]# kubectl create -f pod-demo.yaml
pod/pod-demo created
[root@k8s-master manfests]#
[root@k8s-master manfests]# kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-demo 2/2 Running 0 15s
[root@k8s-master manfests]# kubectl describe pods pod-demo #查看pod详细信息
[root@k8s-master manfests]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-demo 2/2 Running 0 102s 10.244.1.17 k8s-node1 <none> <none>
[root@k8s-master manfests]#
[root@k8s-master manfests]# curl 10.244.1.17
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
[root@k8s-master manfests]#
[root@k8s-master manfests]# kubectl logs pod-demo myapp #查看pod-demo下myapp的日志
10.244.0.0 - - [03/Sep/2019:02:32:52 +0000] "GET / HTTP/1.1" 200 65 "-" "curl/7.29.0" "-"
[root@k8s-master manfests]#
[root@k8s-master manfests]# kubectl exec -it pod-demo -c myapp -- /bin/sh #进入myapp容器
/ #

Pod资源spec的containers字段解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master ~]# kubectl explain pods.spec.containers
name <string> 指定容器名称

image <string> 指定容器所需镜像仓库及镜像名,例如ikubernetes/myapp:v1

imagePullPolicy <string> (可取以下三个值Always,Never,IfNotpresent)
Always:镜像标签为“latest”时,总是去指定的仓库中获取镜像
Never:禁止去仓库中下载镜像,即仅使用本地镜像
IfNotpresent:如果本地没有该镜像,则去镜像仓库中下载镜像

ports <[]Object> 值是一个列表,由一到多个端口对象组成。例如:(名称(可后期调用) 端口号 协议 暴露在的地址上) 暴露端口只是提供额外信息的,不能限制系统是否真的暴露
containerPort <integer> 指定暴露的容器端口
name <string> 当前端口的名称
hostIP <string> 主机端口要绑定的主机IP
hostPort <integer> 主机端口,它将接收到请求通过NAT转发至containerPort字段指定的端口
protocol <string> 端口的协议,默认是TCP

args <[]string> 传递参数给command 相当于docker中的CMD

command <[]string> 相当于docker中的ENTRYPOINT

镜像中的命令和pod中定义的命令关系说明:

  • 如果pod中没有提供command或者args,则使用docker中的CMDENTRYPOINT

  • 如果pod中提供了command但不提供args,则使用提供的command,忽略docker中的CmdEntrypoint

  • 如果pod中只提供了args,则args将作为参数提供给docker中的Entrypoint使用。

  • 如果pod中同时提供了commandargs,则docker中的cmdEntrypoint将会被忽略,pod中的args将最为参数给cmd使用。

Service

概念

Kubernetes Pod是平凡的,由Deployment等控制器管理的Pod对象都是有生命周期的,它们会被创建,也会意外挂掉。虽然它们可以由控制器自动重建或者滚动更新,但是重建或更新之后的Pod对象的IP地址等都会发生新的变化。这样就会导致一个问题,如果一组Pod(称为backend)为其它Pod(称为 frontend)提供服务,那么那些frontend该如何发现,并连接到这组Pod中的哪些backend呢? 这时候就用到了:Service

Service资源基于标签选择器将一组Pod定义成一个逻辑组合,并通过自己的IP地址和端口调度代理请求至组内的Pod对象之上,如下图所示,它向客户端隐藏了真实的、处理用户请求的Pod资源,使得客户端的请求看上去就像是由Service直接处理并响应一样。

Service对象的IP地址也称为Cluster IP,它位于Kubernetes集群配置指定专用IP地址的范围之内,是一种虚拟IP地址,它在Service对象创建后既保持不变,并且能够被同一集群中的Pod资源所访问。

Service端口用于接收客户端请求并将其转发至其后端的Pod中的相应端口之上,因此,这种代理机构也称为“端口代理”(port proxy)或四层代理,工作于TCP/IP协议栈的传输层。

Service资源会通过API Server持续监视着(watch)标签选择器匹配到的后端Pod对象,并实时跟踪各对象的变动,例如,IP地址变动、对象增加或减少等。Service并不直接链接至Pod对象,它们之间还有一个中间层——Endpoints资源对象,它是一个由IP地址和端口组成的列表,这些IP地址和端口则来自由Service的标签选择器匹配到的Pod资源。当创建service对象时,其关联的Endpoints对象会自动创建。

Service能够提供负载均衡的能力,但是在使用上有以下限制:

  • 只提供4层负载均衡能力,而没有7层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上4层负载均衡是不支持的

虚拟IP和服务代理

一个Service对象就是工作节点上的一些iptablesipvs规则,用于将到达Service对象IP地址的流量调度转发至相应的Endpoints对象指定的IP地址和端口之上。kube-proxy组件通过API Server持续监控着各Service及其关联的Pod对象,并将其创建或变动实时反映到当前工作节点上的iptables规则或ipvs规则上。

ipvs是借助于Netfilter实现的网络请求报文调度框架,支持rrwrrlcwlcshsednq等十余种调度算法,用户空间的命令行工具是ipvsadm,用于管理工作与ipvs之上的调度规则。

userspace

这种模式下,kube-proxy负责跟踪API ServerServiceEndpoints对象的变动(创建或移除),并据此调整Service资源的定义。对于每个Service对象,它会随机打开一个本地端口(运行于用户控件的kube-proxy进程负责监听),任何到达此端口的连接请求都将代理至当前Service资源后端的各Pod对象上,至于会挑选中哪个Pod对象则取决于当前Service资源的调度方式(通过ServiceSessionAffinity来确定),默认的调度算法是轮循(round-robin)。

其代理的过程是:请求到达service后,其被转发至内核空间,经由套接字送往用户空间的kube-proxy,而后再由它送回内核空间,并调度至后端Pod

iptables

iptables代理模式中,kube-proxy负责跟踪API ServerServiceEndpoints对象的变动(创建或移除),并据此作出Service资源定义的变动。同时,对于每个Service对象,它都会创建iptables规则直接捕获到达Cluster IP(虚拟IP)和Port的流量,并将其重定向至当前Service的后端。对于每个Endpoints对象,Service资源会为其创建iptables规则并关联至挑选的后端Pod资源,默认的调度算法是随机调度(random)。实现基于客户端IP的会话亲和性(来自同一个用户的请求始终调度到后端固定的一个Pod),可将service.spec.sessionAffinity的值设置为“ClientIP”(默认值为“None”)。

其代理过程是:请求到达service后,其请求被相关service上的iptables规则进行调度和目标地址转换(DNAT)后再转发至集群内的Pod对象之上。

相对userspace模式来说,iptables模式无须将流量在用户空间和内核空间来回切换,因而更加高效和可靠。其缺点是iptables代理模型不会在被挑中的Pod资源无响应时自动进行重定向;而userspace模式则可以。

ipvs

kube-proxy跟踪API ServerServiceEndpoints对象的变动,据此来调用netlink接口创建ipvs规则,并确保与API Server中的变动保持同步,其请求流量的调度功能由ipvs实现,其余的功能由iptables实现。ipvs支持众多调度算法,如rrlcdhshsednq等。

创建Service对象

创建Service对象的常用方法有两种,一是直接使用命令“kubectl expose”命令,二是使用资源清单配置文件。定义Service资源清单文件时,spec的两个较为常用的内嵌字段分别是selectorport,分别用于定义使用的标签选择器和要暴露的端口。

命令方式定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master ~]# kubectl run nginx --image=nginx:1.12 --replicas=3    #创建pod资源指定副本数量为3个
[root@k8s-master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-67685f79b5-688s7 1/1 Running 0 5s 10.244.2.61 k8s-node2 <none> <none>
nginx-67685f79b5-gpc2f 1/1 Running 0 5s 10.244.1.63 k8s-node1 <none> <none>
nginx-67685f79b5-grlrz 1/1 Running 0 5s 10.244.2.60 k8s-node2 <none> <none>
[root@k8s-master ~]# kubectl get deployment #查看deployment控制器资源
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 3/3 3 3 35s

# 下面这条命令表示为deployment控制器资源nginx创建一个service对象,并取名为nginx、端口为80、pod内暴露端口80、协议为TCP协议。
[root@k8s-master ~]# kubectl expose deployment nginx --name=nginx --port=80 --target-port=80 --protocol=TCP
service/nginx exposed
[root@k8s-master ~]# kubectl get service #查看创建的service资源
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27d
nginx ClusterIP 10.104.116.156 <none> 80/TCP 9s

[root@k8s-master ~]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.1.31:6443 27d
nginx 10.244.1.63:80,10.244.2.60:80,10.244.2.61:80 29s

资源清单定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@k8s-master ~]# vim manfests/service-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: service-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: service-deploy-demo
template:
metadata:
name: svc-deploy
labels:
app: service-deploy-demo
spec:
containers:
- name: svc-pod
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80

---
#定义service
apiVersion: v1
kind: Service
metadata:
name: service-demo #service名称
spec:
selector: #用于匹配后端的Pod资源对象,需和上面定义pod的标签一致
app: service-deploy-demo
ports:
- port: 80 #service端口号
targetPort: 80 #后端Pod端口号
protocol: TCP #使用的协议

创建并查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master ~]# kubectl apply -f manfests/service-demo.yaml    #创建资源对象
deployment.apps/service-deploy created
service/service-demo created
[root@k8s-master ~]# kubectl get svc #查看service资源对象,"kubectl get svc"等于"kubectl get service"
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27d
nginx ClusterIP 10.104.116.156 <none> 80/TCP 80m
service-demo ClusterIP 10.98.31.157 <none> 80/TCP 7s
[root@k8s-master ~]# kubectl get pods -o wide -l app=service-deploy-demo #查看pod资源对象
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
service-deploy-66548cc57f-982cd 1/1 Running 0 15s 10.244.2.63 k8s-node2 <none> <none>
service-deploy-66548cc57f-blnvg 1/1 Running 0 15s 10.244.1.67 k8s-node1 <none> <none>
service-deploy-66548cc57f-vcmxb 1/1 Running 0 15s 10.244.2.62 k8s-node2 <none> <none>
[root@k8s-master ~]# kubectl get endpoints service-demo 查看生成的endpoints对应关系
NAME ENDPOINTS AGE
service-demo 10.244.1.67:80,10.244.2.62:80,10.244.2.63:80 43s

节点访问测试

1
2
3
4
5
6
7
8
9
10
[root@k8s-master ~]# kubectl run busybox --image=busybox --rm -it -- /bin/sh    #使用busybox创建一个临时pod客户端
/ # wget -O - -q http://10.98.31.157/ #访问上面创建的service对象的Cluster IP
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
/ # for i in 1 2 3 4; do wget -O - -q http://10.98.31.157/hostname.html; done #循环访问测试站点下的hostname.html页面,可以看出是轮循的分配给后端的pod资源。
service-deploy-66548cc57f-982cd
service-deploy-66548cc57f-blnvg
service-deploy-66548cc57f-982cd
service-deploy-66548cc57f-982cd

#说明:myapp容器中的“/hostname.html"页面能够输出当前容器的主机名。

Service会话粘性

Service资源支持Session affinity)机制,能够将来自同一个客户端的请求始终转发至同一个后端的Pod对象。这意味着会影响调度算法的流量分发功能,进而降低其负载均衡的效果。所以,当客户端访问pod中的应用程序时,如果有基于客户端身份保存某些私有信息,并基于这些私有信息追踪用户的活动等一类的需求时,就可以启用session affinity机制。

Session affinity的效果仅在一段时间期限内生效,默认值为10800秒,超出此时长之后,客户端的再次访问会被调度算法重新调度。Service资源的Session affinity机制仅能基于客户端的IP地址识别客户端身份,把经由同一个NAT服务器进行源地址转换的所有客户端识别为同一个客户端,便导致调度效果不佳,所以,这种方法并不常用。

Service资源通过service.spec.sessionAffinityservice.spec.sessionAffinityConfig两个字段配置粘性会话。sessionAffinity字段用于定义要使用的粘性会话的类型,仅支持使用“None”“ClientIp”两种属性值。

  • None:不使用sessionAffinity,默认值。

  • ClientIP:基于客户端IP地址识别客户端身份,把来自同一个源IP地址的请求始终调度至同一个Pod对象。

类型

  • Clusterlp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP

    clusterlp 主要在每个 node 节点使用 iptables,将发向 clusterlP 对应端口的数据,转发到kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口

  • NodePort:在 Clusterlp 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 <NodelP>:NodePort 来访问该服务

    通过每个Node上的IP和静态端口(NodePort)暴露服务,会自动为Service分配集群IP地址,并将此作为NodePort的路有目标。通过请求<NodePort>:<NodePort> --> <ClusterIP>:<ClusterPort> --> <PodIP>:<ContainerPort>访问到一个NodePort服务

  • LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到<NodelP>: NodePort

  • ExternaIName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建

    通过返回CNAME和它的值,可以将服务映射到externalName字段的内容。换言之,此种类型并非定义由Kubernetes集群提供的服务,而是把集群外部的某服务以DNS CNAME记录的方式映射到集群内,从而让集群内的Pod资源能够访问外部的Service的一种实现方式。这种类型的Service没有ClusterIPNodePort,也没有标签选择器用于选择Pod资源,因此也不会有Endpoints存在。

ClusterIP示例

编写配置清单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@k8s-master ~]# vim manfests/redis-svc.yaml    #编写yaml格式的清单文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis-pod
image: redis
ports:
- name: redis
containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-svc #service对象名
spec:
type: ClusterIP #这里指定使用ClusterIP,默认也是ClusterIP,这里可有可无
selector:
app: redis #匹配上面定义的pod资源
ports:
- port: 6379 #service端口
targetPort: 6379 #后端pod端口
protocol: TCP #协议

[root@k8s-master ~]# kubectl apply -f manfests/redis-svc.yaml #创建资源对象
deployment.apps/redis-deploy created
service/redis-svc created

查看创建的资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@k8s-master ~]# kubectl get svc    #查看service资源
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27d
nginx ClusterIP 10.104.116.156 <none> 80/TCP 17h
redis-svc ClusterIP 10.102.44.127 <none> 6379/TCP 8s
service-demo ClusterIP 10.98.31.157 <none> 80/TCP 16h

[root@k8s-master ~]# kubectl get pods -l app=redis -o wide #查看标签app=redis的pod资源
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
redis-deploy-6559cc4c4c-5v7kx 1/1 Running 0 33s 10.244.2.65 k8s-node2 <none> <none>
redis-deploy-6559cc4c4c-npdtf 1/1 Running 0 33s 10.244.1.69 k8s-node1 <none> <none>

[root@k8s-master ~]# kubectl describe svc redis-svc #查看redis-svc资源对象详细信息
Name: redis-svc
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"redis-svc","namespace":"default"},"spec":{"ports":[{"port":6379,"...
Selector: app=redis
Type: ClusterIP
IP: 10.102.44.127
Port: <unset> 6379/TCP
TargetPort: 6379/TCP
Endpoints: 10.244.1.69:6379,10.244.2.65:6379 #可以看出这里已经和上面的pod资源绑定
Session Affinity: None
Events: <none>

集群内部进行测试

1
2
3
4
5
6
7
8
9
10
#(1)集群内部的节点上面测试
[root@k8s-master ~]# redis-cli -h 10.102.44.127
10.102.44.127:6379> ping
PON

#(2)在后端pod上面进行测试
[root@k8s-master ~]# kubectl exec redis-deploy-6559cc4c4c-5v7kx -it -- /bin/sh
# redis-cli -h 10.102.44.127
10.102.44.127:6379> ping
PONG

NodePort示例

编写配置清单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@k8s-master ~]# vim manfests/nginx-svc.yaml    #编写yaml格式的清单文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-pod
image: nginx:1.12
ports:
- name: nginx
containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc #service对象名
spec:
type: NodePort #这里指定使用ClusterIP,默认也是ClusterIP,这里可有可无
selector:
app: nginx #匹配上面定义的pod资源
ports:
- port: 80 #service端口
targetPort: 80 #后端pod端口
nodePort: 30080 #节点端口
protocol: TCP #协议

[root@k8s-master ~]# kubectl apply -f manfests/nginx-svc.yaml #创建资源对象
deployment.apps/nginx-deploy created
service/nginx-svc created

查看创建的资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@k8s-master ~]# kubectl get svc    #查看service资源
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27d
nginx-svc NodePort 10.105.21.137 <none> 80:30080/TCP 4s
redis-svc ClusterIP 10.102.44.127 <none> 6379/TCP 55m
service-demo ClusterIP 10.98.31.157 <none> 80/TCP 16h

[root@k8s-master ~]# kubectl get pods -l app=nginx -o wide #查看标签app=nginx的pod资源
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deploy-b6f876447-nlv6h 1/1 Running 0 33s 10.244.1.71 k8s-node1 <none> <none>
nginx-deploy-b6f876447-xmn2t 1/1 Running 0 33s 10.244.2.66 k8s-node2 <none> <none>

[root@k8s-master ~]# kubectl describe svc nginx-svc #查看nginx-svc资源对象详细信息
Name: nginx-svc
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"nginx-svc","namespace":"default"},"spec":{"ports":[{"nodePort":30...
Selector: app=nginx
Type: NodePort
IP: 10.105.21.137
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 30080/TCP #这里可以看到多了NodePort且端口为30080
Endpoints: 10.244.1.71:80,10.244.2.66:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>

集群外部进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@courtoap ~]# curl 192.168.1.31:30080    #访问集群master节点的30080端口
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
[root@courtoap ~]# curl 192.168.1.32:30080 #访问集群node节点的30080端口
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>

Headless示例

Service对象隐藏了各Pod资源,并负责将客户端请求流量调度至该组Pod对象之上,但也可能存在客户端直接访问Service资源后端的所有Pod资源,这时就应该向客户端暴露每个Pod资源的IP地址,而不是中间层Service对象的ClusterIP,这种类型的Service资源便称为Headless Service(无头服务)

Headless Service对象没有ClusterIP,因此便没有相关负载均衡或代理问题,其如何为此类Service配置IP地址,其取决于标签选择器的定义

  • 具有标签选择器:端点控制器(Endpoints Controller)会在API中为其创建Endpoints记录,并将ClusterDNS服务中的A记录直接解析到此Service后端的各Pod对象的IP地址上。
  • 没有标签选择器:端点控制器(Endpoints Controller)不会再API中为其创建Endpoints记录,ClusterDNS的配置分为两种情形,对ExternalName类型的服务创建CNAME记录,对其他三种类型来说,为那些与当前Service共享名称的所有Endpoints对象创建一条记录。

编写配置清单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@k8s-master ~]# vim manfests/httpd-svc-headless.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd-pod
image: httpd
ports:
- name: httpd
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpd-svc #service对象名
spec:
clusterIP: None #将ClusterIP字段设置为None即表示为headless类型的service资源对象
selector:
app: httpd #匹配上面定义的pod资源
ports:
- port: 80 #service端口
targetPort: 80 #后端pod端口
protocol: TCP #协议

[root@k8s-master ~]# kubectl apply -f manfests/httpd-svc-headless.yaml
deployment.apps/httpd-deploy created
service/httpd-svc created

查看创建的资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@k8s-master ~]# kubectl get svc    #查看service资源
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc ClusterIP None <none> 80/TCP 4s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27d
nginx-svc NodePort 10.105.21.137 <none> 80:30080/TCP 112m
redis-svc ClusterIP 10.102.44.127 <none> 6379/TCP 168m
service-demo ClusterIP 10.98.31.157 <none> 80/TCP 18h

[root@k8s-master ~]# kubectl get pods -l app=httpd -o wide #查看标签app=httpd的pod资源
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-deploy-5494485b74-4vx64 1/1 Running 0 27s 10.244.2.72 k8s-node2 <none> <none>
httpd-deploy-5494485b74-j6hwm 1/1 Running 0 27s 10.244.2.71 k8s-node2 <none> <none>
httpd-deploy-5494485b74-jn48q 1/1 Running 0 27s 10.244.1.74 k8s-node1 <none> <none>

[root@k8s-master ~]# kubectl describe svc/httpd-svc #查看httpd-svc资源对象详细信息
Name: httpd-svc
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"httpd-svc","namespace":"default"},"spec":{"clusterIP":"None","por...
Selector: app=httpd
Type: ClusterIP
IP: None
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.74:80,10.244.2.71:80,10.244.2.72:80
Session Affinity: None
Events: <none>

测试资源发现

由Headless Service工作特性可知,它记录于ClusterDNS的A记录的相关解析结果是后端Pod资源的IP地址。意味着客户端通过Service资源的名称发现的是各Pod资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#(1)通过创建一个专用的测试Pod资源对象,而后通过其交互式接口进行测试
[root@k8s-master ~]# kubectl run cirror-$RANDOM --rm -it --image=cirros -- /bin/sh
/ # nslookup httpd-svc
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name: httpd-svc
Address 1: 10.244.2.71 10-244-2-71.httpd-svc.default.svc.cluster.local
Address 2: 10.244.1.74 10-244-1-74.httpd-svc.default.svc.cluster.local
Address 3: 10.244.2.72 10-244-2-72.httpd-svc.default.svc.cluster.local

#(2)直接在kubernetes集群上解析
[root@k8s-master ~]# dig -t A httpd-svc.default.svc.cluster.local. @10.96.0.10
......
;; ANSWER SECTION:
httpd-svc.default.svc.cluster.local. 26 IN A 10.244.2.72
httpd-svc.default.svc.cluster.local. 26 IN A 10.244.2.71
httpd-svc.default.svc.cluster.local. 26 IN A 10.244.1.74
......

服务发现Ingress

Kubernetes提供了两种内建的云端负载均衡机制(cloud load balancing)用于发布公共应用,一种是工作于传输层的Service资源,它实现的是“TCP负载均衡器”,另一种是Ingress资源,它实现的是“HTTP(S)负载均衡器”

HTTP(S)负载均衡器是应用层负载均衡机制的一种,支持根据环境做出更好的调度决策。与传输层调度器相比,它提供了诸如可自定义URL映射和TLS卸载等功能,并支持多种类型的后端服务器健康状态检查机制。

Ingress概述

通常情况下,service和pod仅可在集群内部网络中通过IP地址访问。所有到达边界路由器的流量或被丢弃或被转发到其他地方。从概念上讲,可能像下面这样:

Ingress是授权入站连接到达集群服务的规则集合。

可以给Ingress配置提供外部可访问的URL、负载均衡、SSL、基于名称的虚拟主机等。用户通过POST Ingress资源到API Server的方式来请求Ingress。Ingress controller负责实现Ingress,通常使用负载平衡器,它还可以配置边界路由和其他前端,这有助于以HA方式处理流量。

Ingress和Ingress Controller

Ingress是Kubernetes API的标准资源类型之一,它其实就是一组基于DNS名称(host)或URL路径把请求转发至指定的Service资源的规则,用于将集群外部的请求流量转发至集群内部完成服务发布。然而,Ingress资源自身并不能进行“流量穿透”,它仅是一组路由规则的集合,这些规则要想真正发挥作用还需要其他功能的辅助,如监听某套接字,然后根据这些规则的匹配机制路由请求流量。这种能够为Ingress资源监听套接字并转发流量的组件称为Ingress控制器(Ingress Controller)

Ingress控制器并不直接运行为kube-controller-manager的一部分,它是Kubernetes集群的一个重要组件,类似CoreDNS,需要在集群上单独部署

Ingress工作流程

流量到达外部负载均衡器后,首先转发至Service资源Ingres-nginx上,然后通过Ingress控制器基于Ingress资源定义的规则将客户端请求流量直接转发至与Service对应的后端Pod资源之上。这种转发机制会绕过Service资源(app Service;api Service),从而省去了由kube-proxy实现的端口代理开销。Ingress规则需要由一个Service资源对象辅助识别相关的所有Pod资源

Ingress清单文件几个字段说明

Ingress资源是基于HTTP虚拟主机或URL的转发规则,spec字段中嵌套了rules、backend、tls等字段进行定义。下面这个示例中,它包含了一个转发规则,把发往www.ilinux.io的请求代理给名为myapp-svc的Service资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-demo
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: www.ilinux.io
http:
paths:
- backend:
serviceName: myapp-svc
servicePort: 80

#说明:上面资源清单文件中的annotations用于识别其所属的Ingress控制器的类别,这一点在集群上部署多个Ingress控制器时尤为重要。

Ingress Spec(# kubectl explain ingress.spec)中的字段是定义Ingress资源的核心组成部分,主要嵌套如下三个字段:

  • rules <[]Object>:用于定义当前Ingress资源的转发规则列表;未由rules定义规则,或者没有匹配到任何规则时,所有流量都会转发到由backend定义的默认后端。
  • backend <Object>:默认的后端用于服务那些没有匹配到任何规则的请求;定义Ingress资源时,至少应该定义backend或rules两者之一;此字段用于让负载均衡器指定一个全局默认的后端。
  • tls <[]Object>:TLS配置,目前仅支持通过默认端口443提供服务;如果要配置指定的列表成员指向了不同的主机,则必须通过SNI TLS扩展机制来支持此功能。

ingress.spec.rules.http.paths.backend对象的定义由两个必须的内嵌字段组成:serviceName和servicePort,分别用于指定流量转发的后端目标Service资源的名称和端口

部署Ingress Controller(Nginx)

Ingress 控制器自身是运行于Pod中的容器应用,一般是Nginx或Envoy一类的具有代理及负载均衡功能的守护进程,它监视着来自API Server的Ingress对象状态,并根据规则生成相应的应用程序专有格式的配置文件并通过重载或重启守护进程而使新配置生成

Ingress控制器其实就是托管于Kubernetes系统之上的用于实现在应用层发布服务的Pod资源,跟踪Ingress资源并实时生成配置规则

运行为Pod资源的Ingress控制器进程通过下面两种方式接入外部请求流量:

  • 以Deployment控制器管理Ingress控制器的Pod资源,通过NodePort或LoadBalancer类型的Service对象为其接入集群外部的请求流量,这就意味着,定义一个Ingress控制器时,必须在其前端定义一个专用的Service资源。
  • 借助于DaemonSet控制器,将Ingress控制器的Pod资源各自以单一实例的方式运行于集群的所有或部分工作节点之上,并配置这类Pod对象以HostPort或HostNetwork的方式在当前节点接入外部流量

Ingress-nginx官网

Ingress-nginx GitHub仓库地址

Ingress安装文档

在github上下载配置清单yaml文件,并创建部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master ~]# mkdir ingress-nginx   #这里创建一个目录专门用于ingress-nginx(可省略)
[root@k8s-master ~]# cd ingress-nginx/
[root@k8s-master ingress-nginx]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml #下载配置清单yaml文件
[root@k8s-master ingress-nginx]# ls #查看下载的文件
mandatory.yaml

[root@k8s-master ingress-nginx]# kubectl apply -f mandatory.yaml #创建Ingress
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master ingress-nginx]# kubectl get pods -n ingress-nginx    #查看生成的pod,注意这里在ingress-nginx名称空间
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-79f6884cf6-5fb6v 1/1 Running 0 18m
[root@k8s-master ingress-nginx]# kubectl describe pod nginx-ingress-controller-79f6884cf6-5fb6v -n ingress-nginx 查看该pod的详细信息
Name: nginx-ingress-controller-79f6884cf6-5fb6v
Namespace: ingress-nginx
Priority: 0
Node: k8s-node2/192.168.1.33
Start Time: Fri, 27 Sep 2019 17:53:07 +0800
Labels: app.kubernetes.io/name=ingress-nginx
app.kubernetes.io/part-of=ingress-nginx
pod-template-hash=79f6884cf6
Annotations: prometheus.io/port: 10254
prometheus.io/scrape: true
Status: Running
IP: 10.244.2.73
......

如果是裸机部署,还需要安装service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---同样去官网下载配置清单文件,也可以自定义创建。
[root@k8s-master ingress-nginx]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/service-nodeport.yaml
[root@k8s-master ingress-nginx]# kubectl apply -f service-nodeport.yaml #创建service资源
service/ingress-nginx created
[root@k8s-master ingress-nginx]# kubectl get svc -n ingress-nginx #查看service资源
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.107.40.182 <none> 80:32699/TCP,443:30842/TCP 9s
[root@k8s-master ingress-nginx]# kubectl describe svc/ingress-nginx -n ingress-nginx #查看该service的详细信息
Name: ingress-nginx
Namespace: ingress-nginx
Labels: app.kubernetes.io/name=ingress-nginx
app.kubernetes.io/part-of=ingress-nginx
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app.kubernetes.io/name":"ingress-nginx","app.kubernetes.io/par...
Selector: app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx
Type: NodePort
IP: 10.107.40.182
Port: http 80/TCP
TargetPort: 80/TCP
NodePort: http 32699/TCP
Endpoints: 10.244.2.73:80
Port: https 443/TCP
TargetPort: 443/TCP
NodePort: https 30842/TCP
Endpoints: 10.244.2.73:443
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>

示例:使用Ingress发布Nginx

首先创建一个单独的目录为了方便管理

1
2
[root@k8s-master ~]# mkdir ingress-nginx/ingress
[root@k8s-master ~]# cd ingress-nginx/ingress/

创建testing名称空间(也可以使用命令直接创建# kubectl create namespace my-namespace,不过这里使用资源清单格式创建)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master ingress]# vim namespace-testing.yaml    #编写namespace清单文件
apiVersion: v1
kind: Namespace
metadata:
name: testing
labels:
env: testing
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl apply -f namespace-testing.yaml #创建namespace
namespace/testing created
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl get namespace testing #验证
NAME STATUS AGE
testing Active 12s

部署nginx实例,这里使用Deployment控制器于testing中部署nginx相关的Pod对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@k8s-master ingress]# vim deployment-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-nginx
namespace: testing
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- name: http
containerPort: 80
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl apply -f deployment-nginx.yaml
deployment.apps/deploy-nginx created
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl get deploy -n testing
NAME READY UP-TO-DATE AVAILABLE AGE
deploy-nginx 3/3 3 3 5s
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl get pods -n testing
NAME READY STATUS RESTARTS AGE
deploy-nginx-686bddcb56-9g7pq 1/1 Running 0 6s
deploy-nginx-686bddcb56-gqpm2 1/1 Running 0 6s
deploy-nginx-686bddcb56-vtwkq 1/1 Running 0 6s

创建Service资源,关联后端的Pod资源。这里通过service资源svc-nginx的80端口去暴露容器的80端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@k8s-master ingress]# vim service-nginx.yaml
apiVersion: v1
kind: Service
metadata:
name: svc-nginx
namespace: testing
labels:
app: svc-nginx
spec:
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl apply -f service-nginx.yaml
service/svc-nginx created
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl get svc -n testing
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc-nginx ClusterIP 10.99.233.90 <none> 80/TCP 6s
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl describe svc/svc-nginx -n testing
Name: svc-nginx
Namespace: testing
Labels: app=svc-nginx
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"svc-nginx"},"name":"svc-nginx","namespace":"testing"},"s...
Selector: app=nginx
Type: ClusterIP
IP: 10.99.233.90
Port: http 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.76:80,10.244.1.77:80,10.244.2.74:80
Session Affinity: None
Events: <none>

创建Ingress资源,匹配Service资源svc-nginx,并将svc-nginx的80端口暴露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@k8s-master ingress]# vim ingress-nginx.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
namespace: testing
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: nginx.ilinux.io
http:
paths:
- path:
backend:
serviceName: svc-nginx
servicePort: 80
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl apply -f ingress-nginx.yaml
ingress.extensions/nginx created
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl get ingress -n testing
NAME HOSTS ADDRESS PORTS AGE
nginx nginx.ilinux.io 80 16s
[root@k8s-master ingress]#
[root@k8s-master ingress]# kubectl describe ingress -n testing
Name: nginx
Namespace: testing
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
tomcat.ilinux.io
svc-nginx:80 (10.244.1.76:80,10.244.1.77:80,10.244.2.74:80)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"nginx","namespace":"testing"},"spec":{"rules":[{"host":"nginx.ilinux.io","http":{"paths":[{"backend":{"serviceName":"svc-nginx","servicePort":80},"path":null}]}}]}}

kubernetes.io/ingress.class: nginx
Events: <none>

测试,通过Ingress控制器的前端的Service资源的NodePort来访问此服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#首先查看前面部署Ingress控制器的前端的Service资源的映射端口
[root@k8s-master ingress-nginx]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.107.40.182 <none> 80:32699/TCP,443:30842/TCP 3m59s

#终端测试,添加hosts
[root@k8s-master ~]# cat /etc/hosts
192.168.1.31 k8s-master nginx.ilinux.io
192.168.1.32 k8s-node1 nginx.ilinux.io
192.168.1.33 k8s-node2 nginx.ilinux.io
#访问测试
[root@k8s-master ~]# curl nginx.ilinux.io:32699
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
......

验证是否调度到后端的Pod资源,查看日志

1
2
3
4
5
6
7
8
[root@k8s-master ~]# kubectl get pods -n testing
NAME READY STATUS RESTARTS AGE
deploy-nginx-686bddcb56-9g7pq 1/1 Running 0 56m
deploy-nginx-686bddcb56-gqpm2 1/1 Running 0 56m
deploy-nginx-686bddcb56-vtwkq 1/1 Running 0 56m
[root@k8s-master ~]# kubectl logs deploy-nginx-686bddcb56-9g7pq -n testing
10.244.2.75 - - [28/Sep/2019:02:33:45 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "10.244.0.0"
10.244.2.75 - - [28/Sep/2019:02:44:02 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36" "10.244.0.0"

配置TLS Ingress资源(这里使用自签证书)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
1)生成key
[root@k8s-master ingress]# openssl genrsa -out tls.key 2048
2)生成证书
[root@k8s-master ingress]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=ShenZhen/L=ShenZhen/O=DevOps/CN=nginx.ilinux.io -days 3650

3)创建secret资源
[root@k8s-master ingress]# kubectl create secret tls nginx-ingress-secret --cert=tls.crt --key=tls.key -n testing
secret/nginx-ingress-secret created
[root@k8s-master ingress]# kubectl get secret -n testing
NAME TYPE DATA AGE
default-token-lfzrt kubernetes.io/service-account-token 3 116m
nginx-ingress-secret kubernetes.io/tls 2 16s

4)编写Ingress资源清单文件
[root@k8s-master ingress]# vim ingress-nginx-https.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress-tls
namespace: testing
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- nginx.ilinux.io
secretName: nginx-ingress-secret
rules:
- host: nginx.ilinux.io
http:
paths:
- path: /
backend:
serviceName: svc-nginx
servicePort: 80

5)查看Ingress资源信息
[root@k8s-master ingress]# kubectl get ingress -n testing
NAME HOSTS ADDRESS PORTS AGE
nginx nginx.ilinux.io 80 66m
nginx-ingress-tls nginx.ilinux.io 80, 443 15s
[root@k8s-master ingress]# kubectl describe ingress/nginx-ingress-tls -n testing
Name: nginx-ingress-tls
Namespace: testing
Address:
Default backend: default-http-backend:80 (<none>)
TLS:
nginx-ingress-secret terminates nginx.ilinux.io
Rules:
Host Path Backends
---- ---- --------
nginx.ilinux.io
/ svc-nginx:80 (10.244.1.76:80,10.244.1.77:80,10.244.2.74:80)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"nginx-ingress-tls","namespace":"testing"},"spec":{"rules":[{"host":"nginx.ilinux.io","http":{"paths":[{"backend":{"serviceName":"svc-nginx","servicePort":80},"path":"/"}]}}],"tls":[{"hosts":["nginx.ilinux.io"],"secretName":"nginx-ingress-secret"}]}}

kubernetes.io/ingress.class: nginx
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 64s nginx-ingress-controller Ingress testing/nginx-ingress-tls

测试https(这里由于是自签,所以上面提示不安全)

1
2
3
4
#首先查看前面部署Ingress控制器的前端的Service资源的映射端口
[root@k8s-master ingress-nginx]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.107.40.182 <none> 80:32699/TCP,443:30842/TCP 3m59s

示例:使用Ingress发布多个服务

将不同的服务映射不同的主机上

创建一个目录保存本示例的所有资源配置清单

1
2
[root@k8s-master ~]# mkdir ingress-nginx/multi_svc
[root@k8s-master ~]# cd !$

创建名称空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master multi_svc]# vim namespace-ms.yaml    #编写配置清单文件
apiVersion: v1
kind: Namespace
metadata:
name: multisvc
labels:
env: multisvc

[root@k8s-master multi_svc]# kubectl apply -f namespace-ms.yaml #创建上面定义的名称空间
namespace/multisvc created

[root@k8s-master multi_svc]# kubectl get namespace multisvc #查看名称空间
NAME STATUS AGE
multisvc Active 9s

创建后端应用和Service

这里后端应用创建为一组nginx应用和一组tomcat应用

编写资源清单文件,这里将service资源对象和deployment控制器写在这一个文件里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
[root@k8s-master multi_svc]# vim deploy_service-ms.yaml
#tomcat应用的Deployment控制器
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deploy
namespace: multisvc
spec:
replicas: 3
selector:
matchLabels:
app: tomcat
template:
metadata:
labels:
app: tomcat
spec:
containers:
- name: tomcat
image: tomcat:jdk8
imagePullPolicy: IfNotPresent
ports:
- name: httpport
containerPort: 8080
- name: ajpport
containerPort: 8009
---
#tomcat应用的Service资源
apiVersion: v1
kind: Service
metadata:
name: tomcat-svc
namespace: multisvc
labels:
app: tomcat-svc
spec:
selector:
app: tomcat
ports:
- name: httpport
port: 8080
targetPort: 8080
protocol: TCP
- name: ajpport
port: 8009
targetPort: 8009
protocol: TCP

---
#nginx应用的Deployment控制器
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
namespace: multisvc
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
---
#nginx应用的Service资源
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: multisvc
labels:
app: nginx-svc
spec:
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP

创建上面定义资源对象并查看验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[root@k8s-master multi_svc]# kubectl apply -f deploy_service-ms.yaml 
deployment.apps/tomcat-deploy created
service/tomcat-svc created
deployment.apps/nginx-deploy created
service/nginx-svc created
[root@k8s-master multi_svc]# kubectl get pods -n multisvc -o wide #查看pod资源
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deploy-86c667ff66-hl6rx 1/1 Running 0 13s 10.244.2.78 k8s-node2 <none> <none>
nginx-deploy-86c667ff66-hx4j8 1/1 Running 0 13s 10.244.2.77 k8s-node2 <none> <none>
nginx-deploy-86c667ff66-tl9mm 1/1 Running 0 13s 10.244.1.79 k8s-node1 <none> <none>
tomcat-deploy-6484688ddc-n25hn 1/1 Running 0 13s 10.244.1.78 k8s-node1 <none> <none>
tomcat-deploy-6484688ddc-s8dts 1/1 Running 0 13s 10.244.1.80 k8s-node1 <none> <none>
tomcat-deploy-6484688ddc-snszk 1/1 Running 0 13s 10.244.2.76 k8s-node2 <none> <none>
[root@k8s-master multi_svc]# kubectl get svc -n multisvc #查看service资源对象
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc ClusterIP 10.104.213.237 <none> 80/TCP 26s
tomcat-svc ClusterIP 10.103.75.161 <none> 8080/TCP,8009/TCP 26s

[root@k8s-master multi_svc]# kubectl describe svc/nginx-svc -n multisvc #查看service对象nginx-svc的详细信息
Name: nginx-svc
Namespace: multisvc
Labels: app=nginx-svc
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx-svc"},"name":"nginx-svc","namespace":"multisvc"},"...
Selector: app=nginx
Type: ClusterIP
IP: 10.104.213.237
Port: http 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.79:80,10.244.2.77:80,10.244.2.78:80
Session Affinity: None
Events: <none>

[root@k8s-master multi_svc]# kubectl describe svc/tomcat-svc -n multisvc #查看service对象tomcat-svc的详细信息
Name: tomcat-svc
Namespace: multisvc
Labels: app=tomcat-svc
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"tomcat-svc"},"name":"tomcat-svc","namespace":"multisvc"}...
Selector: app=tomcat
Type: ClusterIP
IP: 10.103.75.161
Port: httpport 8080/TCP
TargetPort: 8080/TCP
Endpoints: 10.244.1.78:8080,10.244.1.80:8080,10.244.2.76:8080
Port: ajpport 8009/TCP
TargetPort: 8009/TCP
Endpoints: 10.244.1.78:8009,10.244.1.80:8009,10.244.2.76:8009
Session Affinity: None
Events: <none>

创建Ingress资源对象

编写资源清单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master multi_svc]# vim ingress_host-ms.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: multi-ingress
namespace: multisvc
spec:
rules:
- host: nginx.imyapp.com
http:
paths:
- path: /
backend:
serviceName: nginx-svc
servicePort: 80
- host: tomcat.imyapp.com
http:
paths:
- path: /
backend:
serviceName: tomcat-svc
servicePort: 8080

创建上面定义资源对象并查看验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@k8s-master multi_svc]# kubectl apply -f ingress_host-ms.yaml 
ingress.extensions/multi-ingress created
[root@k8s-master multi_svc]# kubectl get ingress -n multisvc #查看ingress资源对象
NAME HOSTS ADDRESS PORTS AGE
multi-ingress nginx.imyapp.com,tomcat.imyapp.com 80 18s

[root@k8s-master multi_svc]# kubectl describe ingress/multi-ingress -n multisvc #查看ingress资源multi-ingrsss的详细信息
Name: multi-ingress
Namespace: multisvc
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
nginx.imyapp.com
/ nginx-svc:80 (10.244.1.79:80,10.244.2.77:80,10.244.2.78:80)
tomcat.imyapp.com
/ tomcat-svc:8080 (10.244.1.78:8080,10.244.1.80:8080,10.244.2.76:8080)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"multi-ingress","namespace":"multisvc"},"spec":{"rules":[{"host":"nginx.imyapp.com","http":{"paths":[{"backend":{"serviceName":"nginx-svc","servicePort":80},"path":"/"}]}},{"host":"tomcat.imyapp.com","http":{"paths":[{"backend":{"serviceName":"tomcat-svc","servicePort":8080},"path":"/"}]}}]}}

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 39s nginx-ingress-controller Ingress multisvc/multi-ingress

测试访问

这是测试自定义的域名,故需要配置host

1
2
3
192.168.1.31	 nginx.imyapp.com tomcat.imyapp.com
192.168.1.32 nginx.imyapp.com tomcat.imyapp.com
192.168.1.33 nginx.imyapp.com tomcat.imyapp.com

查看部署的Ingress的Service对象的端口

1
2
3
[root@k8s-master multi_svc]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.107.40.182 <none> 80:32699/TCP,443:30842/TCP 6h39m

访问`nginx.imyapp.com:32699

配置Ingress处理TLS传输

这里使用自签证书,通过OpenSSL进行创建

创建证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#创建nginx.imyapp.com域名的证书
[root@k8s-master multi_svc]# openssl genrsa -out nginx.imyapp.com.key 2048
[root@k8s-master multi_svc]# openssl req -new -x509 -key nginx.imyapp.com.key -out nginx.imyapp.com.crt -subj /C=CN/ST=ShenZhen/L=ShenZhen/O=DevOps/CN=nginx.imyapp.com -days 3650

#创建tomcat.imyapp.com域名的证书
[root@k8s-master multi_svc]# openssl genrsa -out tomcat.imyapp.com.key 2048
[root@k8s-master multi_svc]# openssl req -new -x509 -key tomcat.imyapp.com.key -out tomcat.imyapp.com.crt -subj /C=CN/ST=ShenZhen/L=ShenZhen/O=DevOps/CN=tomcat.imyapp.com -days 3650

#查看生成的证书
[root@k8s-master multi_svc]# ll *.com.*
-rw-r--r-- 1 root root 1298 9月 28 17:23 nginx.imyapp.com.crt
-rw-r--r-- 1 root root 1675 9月 28 17:22 nginx.imyapp.com.key
-rw-r--r-- 1 root root 1302 9月 28 17:24 tomcat.imyapp.com.crt
-rw-r--r-- 1 root root 1679 9月 28 17:24 tomcat.imyapp.com.key

创建secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#创建nginx域名的secret
[root@k8s-master multi_svc]# kubectl create secret tls nginx-ingress-secret --cert=nginx.imyapp.com.crt --key=nginx.imyapp.com.key -n multisvc
secret/nginx-ingress-secret created

#创建tomcat域名的secret
[root@k8s-master multi_svc]# kubectl create secret tls tomcat-ingress-secret --cert=tomcat.imyapp.com.crt --key=tomcat.imyapp.com.key -n multisvc
secret/tomcat-ingress-secret created

#查看secret
[root@k8s-master multi_svc]# kubectl get secret -n multisvc
NAME TYPE DATA AGE
default-token-mf5wd kubernetes.io/service-account-token 3 5h12m
nginx-ingress-secret kubernetes.io/tls 2 53s
tomcat-ingress-secret kubernetes.io/tls 2 27s

编写带TLS的Ingress资源清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@k8s-master multi_svc]# cp ingress_host-ms.yaml ingress_host_https-ms.yaml
[root@k8s-master multi_svc]# vim ingress_host_https-ms.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: multi-ingress-https
namespace: multisvc
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- nginx.imyapp.com
secretName: nginx-ingress-secret
- hosts:
- tomcat.imyapp.com
secretName: tomcat-ingress-secret
rules:
- host: nginx.imyapp.com
http:
paths:
- path: /
backend:
serviceName: nginx-svc
servicePort: 80
- host: tomcat.imyapp.com
http:
paths:
- path: /
backend:
serviceName: tomcat-svc
servicePort: 8080

创建ingress资源

1
2
3
4
5
6
[root@k8s-master multi_svc]# kubectl apply -f ingress_host_https-ms.yaml
ingress.extensions/multi-ingress-https created
[root@k8s-master multi_svc]# kubectl get ingress -n multisvc
NAME HOSTS ADDRESS PORTS AGE
multi-ingress nginx.imyapp.com,tomcat.imyapp.com 80 44m
multi-ingress-https nginx.imyapp.com,tomcat.imyapp.com 80, 443 3s

测试,通过Ingress控制器的前端的Service资源的NodePort来访问此服务,上面看到ingress控制器的service资源的443端口对应的节点的30842端口

将不同的服务映射到相同主机的不同路径

在这种情况下,根据请求的URL中的路径,请求将发送到两个不同的服务。因此,客户端可以通过一个IP地址(Ingress 控制器的IP地址)访问两种不同的服务。

注意:这里Ingresspath的定义,需要与后端真实Service提供的Path一致,否则将被转发到一个不存在的path上,引发错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: tomcat-ingress
namespace: multisvc
spec:
rules:
- host: www.imyapp.com
http:
paths:
- path: /nginx
backend:
serviceName: nginx-svc
servicePort: 80
- path: /tomcat
backend:
serviceName: tomcat-svc
servicePort: 8080

存储

概述

Pod本身具有生命周期,这就带了一系列的问题,第一,当一个容器损坏之后,kubelet会重启这个容器,但是文件会丢失-这个容器会是一个全新的状态;第二,当很多容器在同一Pod中运行的时候,很多时候需要数据文件的共享。Docker支持配置容器使用存储卷将数据持久存储于容器自身文件系统之外的存储空间之中,它们可以是节点文件系统或网络文件系统之上的存储空间。相应的,kubernetes也支持类似的存储卷功能,不过,其存储卷是与Pod资源绑定而非容器

简单来说,存储卷是定义在Pod资源之上、可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久能力取决于存储卷自身是否支持持久机制

支持的存储卷类型

Kubernetes支持非常丰富的存储卷类型,包括本地存储(节点)和网络存储系统中的诸多存储机制,还支持SecretConfigMap这样的特殊存储资源。例如,关联节点本地的存储目录与关联GlusterFS存储系统所需要的配置参数差异巨大,因此指定存储卷类型时也就限定了其关联到的后端存储设备。通过命令# kubectl explain pod.spec可以查看当前kubernetes版本支持的存储卷类型。常用类型如下:

  • 非持久性存储

  • emptyDir

  • hostPath

  • 网络连接性存储

  • SAN:iscsi

  • NFS:nfs、cfs

  • 分布式存储

  • glusterfs、cephfs、rbd

  • 云端存储

  • awsElasticBlockStore、azureDisk、gitRepo

使用方式

在Pod中定义使用存储卷的配置由两部分组成:一是通过.spec.volumes字段定义在Pod之上的存储卷列表,其支持使用多种不同类型的存储卷且配置参数差别很大;另一个是通过.spce.containers.volumeMounts字段在容器上定义的存储卷挂载列表,它只能挂载当前Pod资源中定义的具体存储卷,当然,也可以不挂载任何存储卷

在Pod级别定义存储卷时,.spec.volumes字段的值为对象列表格式,每个对象为一个存储卷的定义,由存储卷名称(.spec.volumes.name <string>)或存储卷对象(.spec.volumes.VOL_TYPE <Object>)组成,其中VOL_TYPE是使用的存储卷类型名称,它的内嵌字段随类型的不同而不同。下面示例定义了由两个存储卷组成的卷列表,一个为emptyDir类型,一个是gitRepo类型

1
2
3
4
5
6
7
8
9
......
volumes:
- name: data
emptyDir: {}
- name: example
gitRepo:
repository: https://github.com/ikubernetes/k8s_book.git
revision: master
directory:

无论何种类型的存储卷,挂载格式基本上都是相同的,通过命令# kubectl explain pod.spec.containers.volumeMounts 可以进行查看。在容器中顶一个挂载卷时的通用语法形式如下:

1
2
3
4
5
6
......
volumeMounts:
- name <string> -required- #指定要挂载的存储卷的名称,必选字段
mountPath <string> -required- #挂载点路径,容器文件系统的路径,必选字段
readOnly <boolean> #是否挂载为只读卷
subPath <string> #挂载存储卷时使用的子路径,及mountPath指定的路径下使用一个子路径作为其挂载点。

示例,容器myapp将上面定义的data存储卷挂载于/var/log/myapp,将example挂载到/webdata/example目录

1
2
3
4
5
6
7
8
9
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: data
mountPath: /var/log/myapp/
- name: example
mountPath: /webdata/example/

使用示例

ConfigMap

Secret

Volume

emptyDir 存储卷

emptyDir存储卷是Pod对象生命周期中的一个临时目录,类似于Docker上的“docker 挂载卷”,在Pod对象启动时即被创建,而在Pod对象被移除时会被一并删除(永久删除)。Pod中的容器都可以读写这个目录,这个目录可以被挂载到各个容器相同或者不相同的路径下。注意:一个容器崩溃了不会导致数据的丢失,因为容器的崩溃并不移除Pod

emptyDir的作用:

  1. 普通空间,基于磁盘的数据存储
  2. 作为从崩溃中恢复的备份点
  3. 存储那些需要长久保存的数据,例如web服务中的数据

emptyDir字段说明:

1
2
3
[root@k8s-master ~]# kubectl explain pod.spec.volumes.emptyDir
medium <string>: #此目录所在的存储介质的类型,可取值为“default”或“Memory”,默认为default,表示使用节点的的默认存储介质;Memory表示使用基于RAM的临时的文件系统temfs,空间受限于内存,但性能非常好,通常用于为容器中的应用提供缓存空间。
sizeLimit <string> #当前存储卷的空间限额,默认值为nil,表示不限制;不过,在medium字段值为“Memory”时建议务必定义此限额。

示例

这里定义了一个Pod资源对象(vol-emptydir-pod),在其内部定义了两个容器,其中一个容器是辅助容器sidecar,每隔10秒生成一行信息追加到index.html文件中;另一个是nginx容器,将存储卷挂载到站点目录。然后访问nginxhtml页面验证两个容器之间挂载的emptyDir实现共享

编辑资源清单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@k8s-master storage]# vim vol-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol-emptydir-pod
spec:
volumes: #定义存储卷
- name: html #定义存储卷的名称
emptyDir: {} #定义存储卷的类型
containers:
- name: nginx
image: nginx:1.12
volumeMounts: #在容器中定义挂载存储卷的名和路径
- name: html
mountPath: /usr/share/nginx/html
- name: sidecar
image: alpine
volumeMounts: #在容器中定义挂载存储卷的名和路径
- name: html
mountPath: /html
command: ["/bin/sh", "-c"]
args:
- while true; do
echo $(hostname) $(date) >> /html/index.html;
sleep 10;
done

创建并查看状态

1
2
3
4
5
[root@k8s-master storage]# kubectl apply -f vol-emptydir.yaml 
pod/vol-emptydir-pod created
[root@k8s-master storage]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-emptydir-pod 2/2 Running 0 63s 10.244.2.79 k8s-node2 <none> <none>

访问测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@k8s-master storage]# curl 10.244.2.79
vol-emptydir-pod Wed Oct 9 03:32:43 UTC 2019
vol-emptydir-pod Wed Oct 9 03:32:53 UTC 2019
vol-emptydir-pod Wed Oct 9 03:33:03 UTC 2019
vol-emptydir-pod Wed Oct 9 03:33:13 UTC 2019
vol-emptydir-pod Wed Oct 9 03:33:23 UTC 2019
......

#进入vol-emptydir-pod中的sidecar容器中查看挂载目录下的index.html文件
[root@k8s-master storage]# kubectl exec vol-emptydir-pod -c sidecar -it -- /bin/sh
/ # ls
bin etc html media opt root sbin sys usr
dev home lib mnt proc run srv tmp var
/ # ls /html
index.html
/ # cat /html/index.html
vol-emptydir-pod Wed Oct 9 03:32:43 UTC 2019
vol-emptydir-pod Wed Oct 9 03:32:53 UTC 2019
vol-emptydir-pod Wed Oct 9 03:33:03 UTC 2019
......

#进入vol-emptydir-pod中的nginx容器中查看挂载目录下的index.html文件
[root@k8s-master storage]# kubectl exec vol-emptydir-pod -c nginx -it -- /bin/sh
# cat /usr/share/nginx/html/index.html
vol-emptydir-pod Wed Oct 9 03:32:43 UTC 2019
vol-emptydir-pod Wed Oct 9 03:32:53 UTC 2019
vol-emptydir-pod Wed Oct 9 03:33:03 UTC 2019
......

hostPath 存储卷

hostPath类型的存储卷是指将工作节点上的某文件系统的目录或文件挂载于Pod中的一种存储卷,独立于Pod资源的生命周期,具有持久性。在Pod删除时,数据不会丢失

hostPath字段说明:

1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master storage]# kubectl explain pod.spec.volumes.hostPath
path <string> -required- #指定工作节点上的目录路径
type <string> #指定存储卷类型

type类型如下:
DirectoryOrCreate 指定的路径不存在时自动创建其权限为0755的空目录,属主和属组为kubelet
Directory 必须存在的目录路径
FileOrCreate 指定的路径不存在时自动创建其权限为0644的空文件,属主和属组为kubelet
File 必须存在的文件路径
Socket 必须存在的Socket文件路径
CharDevice 必须存在的字符设备文件路径
BlockDevice 必须存在的块设备文件路径

示例

编辑资源清单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@k8s-master storage]# vim vol-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-hostpath
namespace: default
spec:
containers:
- name: myapp
image: nginx:1.15
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
hostPath:
path: /data/pod/volume1
type: DirectoryOrCreate

创建并查看状态

1
2
3
4
5
[root@k8s-master storage]# kubectl apply -f vol-hostpath.yaml 
pod/vol-hostpath-pod created
[root@k8s-master storage]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-hostpath-pod 1/1 Running 0 7s 10.244.1.83 k8s-node1 <none> <none>

通过上面查看pod被调度到节点1上面,查看节点1的目录并创建测试文件

1
2
3
[root@k8s-node1 ~]# ll /data/pod/volume1/
总用量 0
[root@k8s-node1 ~]# echo "<h1>kubernetes hostPath test</h1>" >> /data/pod/volume1/index.html

访问测试,及删除测试

1
2
3
4
5
6
7
8
9
[root@k8s-master storage]# curl 10.244.1.83
<h1>kubernetes hostPath test</h1>

#删除pod资源再次查看节点1上面的文件
[root@k8s-master storage]# kubectl delete -f vol-hostpath.yaml
pod "vol-hostpath-pod" deleted
[root@k8s-node1 ~]# ll /data/pod/volume1/
总用量 4
-rw-r--r-- 1 root root 34 10月 9 16:09 index.html

nfs 存储卷

nfs存储卷用于将事先存在的NFS服务器上导出的存储空间挂载到Pod中供容器使用。与emptyDir不同的是,当pod资源删除时emptyDir也会被删除,而NFSPod对象删除时仅是被卸载而非删除。这就意味NFS能够允许我们提前对数据进行处理,而且这些数据可以在Pod之间相互传递,并且NFS可以同时被多个Pod挂载并进行读写

nfs字段说明:

1
2
3
4
[root@k8s-master ~]# kubectl explain pod.spec.volumes.nfs
server <string> -required- #NFS服务器的IP地址或主机名,必选字段
path <string> -required- #NFS服务器导出(共享)的文件系统路径,必选字段
readOnly <boolean> #是否以只读方式挂载,默认为false

示例

准备一个nfs服务器

1
2
3
4
5
6
7
8
9
10
[root@storage ~]# yum -y install nfs-utils    #安装软件
[root@storage ~]# mkdir -p /data/k8s/v1 #创建共享目录
[root@storage ~]# vim /etc/exports #编辑配置文件配置共享目录
/data/k8s/v1 192.168.1.0/24(rw,no_root_squash)
[root@storage ~]# systemctl start rpcbind #启动rpcbind服务(nfs依赖服务)
[root@storage ~]# systemctl start nfs #启动nfs

[root@k8s-node1 ~]# showmount -e 192.168.1.34 #k8s节点测试能否正常访问到nfs服务器
Export list for 192.168.1.34:
/data/k8s/v1 192.168.1.0/24

编辑资源清单文件

1
[root@k8s-master storage]# vim vol-nfs.yaml

创建并查看状态

1
2
3
4
5
6
7
8
[root@k8s-master storage]# kubectl apply -f vol-nfs.yaml 
pod/vol-nfs-pod created
[root@k8s-master storage]# kubectl get pods
NAME READY STATUS RESTARTS AGE
vol-nfs-pod 1/1 Running 0 45s
[root@k8s-master storage]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-nfs-pod 1/1 Running 0 51s 10.244.2.80 k8s-node2 <none> <none>

测试验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@k8s-master storage]# kubectl exec -it vol-nfs-pod redis-cli
127.0.0.1:6379> set mykey "hello test"
OK
127.0.0.1:6379> get mykey
"hello test
127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379> exit

#为了测试其数据持久化效果,下面删除Pod资源vol-nfs-pod,并于再次重新创建后检测数据是否依然能够访问
[root@k8s-master storage]# kubectl delete -f vol-nfs.yaml
pod "vol-nfs-pod" deleted
[root@k8s-master storage]# kubectl apply -f vol-nfs.yaml
pod/vol-nfs-pod created
[root@k8s-master storage]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-nfs-pod 1/1 Running 0 47s 10.244.1.84 k8s-node1 <none> <none>
[root@k8s-master storage]# kubectl exec -it vol-nfs-pod redis-cli
127.0.0.1:6379> get mykey
"hello test"
127.0.0.1:6379>

PVC、PV

Kubernetes提供那么多存储接口,但是首先Kubernetes的各个Node节点能管理这些存储,但是各种存储参数也需要专业的存储工程师才能了解,由此我们的Kubernetes管理变的更加复杂。由此kubernetes提出了PVPVC的概念,这样开发人员和使用者就不需要关注后端存储是什么,使用什么参数等问题。如下图:

PV

PersistentVolumePV)是集群中已由管理员配置的一段网络存储。集群中的资源就像一个节点是一个集群资源。PV是诸如卷之类的卷插件,但是具有独立于使用PV的任何单个Pod的生命周期。该API对象捕获存储的实现细节,即NFSISCSI或云提供商特定的存储系统

PVC

PersistentVolumeClaimPVC)是用户存储的请求。它类似于PodPod消耗节点资源,PVC消耗存储资源。Pod可以请求特定级别的资源(CPU和内存)。权限要求可以请求特定的大小和访问模式

虽然PersistentVolumeClaims允许用户使用抽象存储资源,但是常见的是,用户需要具有不同属性(如性能)的PersistentVolumes,用于不同的问题。集群管理员需要能够提供多种不同于PersistentVolumesPersistentVolumes,而不仅仅是大小和访问模式,而不会使用户了解这些卷的实现细节。对于这些需求,存在StorageClass资源

StorageClass为管理员提供了一种描述他们提供的存储的“类”的方法。不同的类可能映射到服务质量级别,或备份策略,或者由集群管理员确定的任意策略。Kubernetes本身对于什么类别代表是不言而喻的。这个概念有时在其它存储系统中称为“配置文件”

生命周期

PV是集群中的资源。PVC是对这些资源的请求。PVPVC之间的相互作用遵循这个生命周期:

1
Provisioning—>Binding—>Using—>Releasing—>Recycling

供应准备Provisioning

PV有两种提供方式:静态或者动态

  • Static:集群管理员创建多个PV。它们携带可供集群用户使用的真实存储的详细信息。它们存在于Kubernetes API中,可用于消费

  • Dynamic:当管理员创建的静态PV都不匹配用户的PersistentVolumesClaim时,集群可能会尝试为PVC动态配置卷。此配置基于StorageClasses:PVC必须请求一个类,并且管理员必须已经创建并配置该类才能进行动态配置。要求该类的声明有效地位自己禁用动态配置

绑定Binding

用户创建PVC并指定需要的资源和访问模式。在找到可用PV之前,PVC会保持未绑定状态

使用Using

用户可在Pod中像volume一样使用PVC

释放Releasing

用户删除PVC来回收存储资源,PV将变成“released”状态。由于还保留着之前的数据,这些数据要根据不同的策略来处理,否则这些存储资源无法被其它PVC使用

回收Recycling

PV可以设置三种回收策略:保留(Retain)、回收(Recycle)和删除(Delete

创建PV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# PersistentVolume Spec主要支持以下几个通用字段,用于定义PV的容量、访问模式、和回收策略
[root@k8s-master ~]# kubectl explain pv.spec
capacity <map[string]string> #当前PV的容量;目前,capacity仅支持空间设定,将来应该还可以指定IOPS和throughput。

accessModes <[]string> #访问模式;尽管在PV层看起来并无差异,但存储设备支持及启用的功能特性却可能不尽相同。例如NFS存储支持多客户端同时挂载及读写操作,但也可能是在共享时仅启用了只读操作,其他存储系统也存在类似的可配置特性。因此,PV底层的设备或许存在其特有的访问模式,用户使用时必须在其特性范围内设定其功能。参考:https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
- ReadWriteOnce:仅可被单个节点读写挂载;命令行中简写为RWO。
- ReadOnlyMany:可被多个节点同时只读挂载;命令行中简写为ROX。
- ReadWriteMany:可被多个节点同时读写挂载;命令行中简写为RWX。

persistentVolumeReclaimPolicy <string> #PV空间被释放时的处理机制;可用类型仅为Retain(默认)、Recycle或Delete,具体说明如下。
- Retain:保持不动,由管理员随后手动回收。
- Recycle:空间回收,即删除存储卷目录下的所有文件(包括子目录和隐藏文件),目前仅NFS和hostPath支持此操作。
- Delete:删除存储卷,仅部分云端存储系统支持,如AWS EBS、GCE PD、Azure Disk和Cinder

volumeMode <string> #卷模型,用于指定此卷可被用作文件系统还是裸格式的块设备;默认为Filesystem。

storageClassName <string> #当前PV所属的StorageClass的名称;默认为空值,即不属于任何StorageClass。

mountOptions <[]string> #挂载选项组成的列表,如ro、soft和hard等。

创建PVC

PersistentVolumeClaim是存储卷类型的资源,它通过申请占用某个PersistentVolume而创建,它与PV是一对一的关系,用户无须关系其底层实现细节。申请时,用户只需要指定目标空间的大小、访问模式、PV标签选择器和StorageClass等相关信息即可。PVCSpec字段的可嵌套字段具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master ~]# kubectl explain pvc.spec
accessModes <[]string> #当前PVC的访问模式,其可用模式与PV相同

resources <Object> #当前PVC存储卷需要占用的资源量最小值;目前,PVC的资源限定仅指其空间大小

selector <Object> #绑定时对PV应用的标签选择器(matchLabels)或匹配条件表达式(matchEx-pressions),用于挑选要绑定的PV;如果同时指定了两种挑选机制,则必须同时满足两种选择机制的PV才能被选出

storageClassName <string> #所依赖的存储卷的名称

volumeMode <string> #卷模型,用于指定此卷可被用作于文件系统还是裸格式的块设备;默认为“Filesystem”

volumeName <string> #用于直接指定要绑定的PV的卷名

在Pod中使用PVC

在Pod资源中调用PVC资源,只需要在定义volumes时使用persistentVolumeClaims字段嵌套指定两个字段即可。具体如下:

1
2
3
[root@k8s-master ~]# kubectl explain pod.spec.volumes.persistentVolumeClaim
claimName <string> -required- #要调用的PVC存储卷的名称,PVC卷要与Pod在同一名称空间中
readOnly <boolean> #是否将存储卷挂载为只读模式,默认为false。

使用PVC和PV

说明:示例中,准备了一台NFS Server创建了几个共享目录提供给Kubernetes作为PV使用。在创建PV的同时指定了不同的大小和不同的访问权限,然后在创建PVC时候指定了大小为6Gi,故满足条件的PV只有pv003~pv005,这里通过标签选择器选择了pv003。Pod中的容器使用了MySQL,并将MySQL的数据目录挂载到PV上。示例图如下:

准备NFS服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(1)创建存储卷对应的目录
[root@storage ~]# mkdir /data/volumes/v{1..5} -p

(2)修改nfs的配置文件
[root@storage ~]# vim /etc/exports
/data/volumes/v1 192.168.1.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.1.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.1.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.1.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.1.0/24(rw,no_root_squash)

(3)查看nfs的配置
[root@storage ~]# exportfs -arv
exporting 192.168.1.0/24:/data/volumes/v5
exporting 192.168.1.0/24:/data/volumes/v4
exporting 192.168.1.0/24:/data/volumes/v3
exporting 192.168.1.0/24:/data/volumes/v2
exporting 192.168.1.0/24:/data/volumes/v1

(4)使配置生效
[root@storage ~]# showmount -e
Export list for storage:
/data/volumes/v5 192.168.1.0/24
/data/volumes/v4 192.168.1.0/24
/data/volumes/v3 192.168.1.0/24
/data/volumes/v2 192.168.1.0/24
/data/volumes/v1 192.168.1.0/24

创建PV;这里创建5个PV,存储大小各不相等,是否可读也不相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
(1)编写资源清单文件
[root@k8s-master storage]# vim pv-nfs-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: 192.168.1.34
readOnly: false
accessModes: ["ReadWriteOnce","ReadWriteMany"]
capacity:
storage: 2Gi
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: 192.168.1.34
readOnly: false
accessModes: ["ReadWriteOnce"]
capacity:
storage: 5Gi
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: 192.168.1.34
readOnly: false
accessModes: ["ReadWriteOnce","ReadWriteMany"]
capacity:
storage: 10Gi
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: 192.168.1.34
readOnly: false
accessModes: ["ReadWriteOnce","ReadWriteMany"]
capacity:
storage: 15Gi
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: 192.168.1.34
readOnly: false
accessModes: ["ReadWriteOnce","ReadWriteMany"]
capacity:
storage: 20Gi
persistentVolumeReclaimPolicy: Retain

(2)创建PV
[root@k8s-master storage]# kubectl apply -f pv-nfs-demo.yaml
persistentvolume/pv-nfs-001 created
persistentvolume/pv-nfs-002 created
persistentvolume/pv-nfs-003 created
persistentvolume/pv-nfs-004 created
persistentvolume/pv-nfs-005 created

(3)查看PV
[root@k8s-master storage]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-nfs-001 2Gi RWO,RWX Retain Available 2s
pv-nfs-002 5Gi RWO Retain Available 2s
pv-nfs-003 10Gi RWO,RWX Retain Available 2s
pv-nfs-004 15Gi RWO,RWX Retain Available 2s
pv-nfs-005 20Gi RWO,RWX Retain Available 2s

创建PVC,绑定PV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
(1)编写资源清单文件
[root@k8s-master storage]# vim vol-nfs-pvc.yaml
#创建PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 6Gi #指定PVC大小为6Gi
selector: #这里通过标签选择器指定了所使用的pv卷为key为name,value为pv003的pv资源
matchLabels:
name: pv003
---
#创建Pod
apiVersion: v1
kind: Pod
metadata:
name: pvc-mysql
labels:
app: mysql
spec:
containers:
- name: pvc-mysql-pod
image: mysql:latest
imagePullPolicy: IfNotPresent
ports:
- name: mysqlport
containerPort: 3306
volumeMounts:
- name: mysqldata
mountPath: /var/lib/mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "mysql"
volumes:
- name: mysqldata
persistentVolumeClaim: #通过该字段定义使用pvc
claimName: nfs-pvc #指定pvc的名称
readOnly: false #关闭只读

(2)创建PVC和Pod
[root@k8s-master storage]# kubectl apply -f vol-nfs-pvc.yaml
persistentvolumeclaim/nfs-pvc created
pod/pvc-mysql created

查询验证pv和pvc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@k8s-master storage]# kubectl get pvc    #查看pvc,可以看到该pvc使用的是pv-nfs-003资源
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nfs-pvc Bound pv-nfs-003 10Gi RWO,RWX 12s

[root@k8s-master storage]# kubectl get pv #查看pv,可以看出pv-nfs-003资源的状态从Availabel变成了Bound
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-nfs-001 2Gi RWO,RWX Retain Available 64m
pv-nfs-002 5Gi RWO Retain Available 64m
pv-nfs-003 10Gi RWO,RWX Retain Bound default/nfs-pvc 64m
pv-nfs-004 15Gi RWO,RWX Retain Available 64m
pv-nfs-005 20Gi RWO,RWX Retain Available 64m

[root@k8s-master storage]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pvc-mysql 1/1 Running 0 31s 10.244.2.84 k8s-node2 <none> <none>

[root@storage ~]# ls /data/volumes/v3/ #查看nfs服务器的pv3对应的共享目录,里面生成了mysql的数据。
auto.cnf ca-key.pem ib_buffer_pool ibtmp1 performance_schema server-key.pem
binlog.000001 ca.pem ibdata1 #innodb_temp private_key.pem sys
binlog.000002 client-cert.pem ib_logfile0 mysql public_key.pem undo_001
binlog.index client-key.pem ib_logfile1 mysql.ibd server-cert.pem undo_002

测试验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#(1)进入到pod连接容器mysql并创建一个数据库
[root@k8s-master ~]# kubectl exec -it pvc-mysql -- mysql -u root -pmysql
......
mysql>
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.01 sec)

mysql> create database volumes;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| volumes |
+--------------------+
5 rows in set (0.00 sec)

mysql> exit
Bye


#(2)删除pvc和pod和pv
[root@k8s-master storage]# kubectl delete -f vol-nfs-pvc.yaml #删除pvc
persistentvolumeclaim "nfs-pvc" deleted
pod "pvc-mysql" deleted
[root@k8s-master storage]# kubectl delete -f pv-nfs-demo.yaml #删除pv(如果有pv在被使用的状态,需要先删除pvc方可删除pv)
persistentvolume "pv-nfs-001" deleted
persistentvolume "pv-nfs-002" deleted
persistentvolume "pv-nfs-003" deleted
persistentvolume "pv-nfs-004" deleted
persistentvolume "pv-nfs-005" deleted
[root@storage ~]# ls /data/volumes/v3/ #上面删除了pv和pvc,可以看出存储服务器上面的数据还是存在
auto.cnf ca-key.pem ib_buffer_pool ibtmp1 performance_schema server-key.pem volumes
binlog.000001 ca.pem ibdata1 #innodb_temp private_key.pem sys
binlog.000002 client-cert.pem ib_logfile0 mysql public_key.pem undo_001
binlog.index client-key.pem ib_logfile1 mysql.ibd server-cert.pem undo_002

#(3)重新创建pv和pvc和pod验证数据
[root@k8s-master storage]# kubectl apply -f pv-nfs-demo.yaml
persistentvolume/pv-nfs-001 created
persistentvolume/pv-nfs-002 created
persistentvolume/pv-nfs-003 created
persistentvolume/pv-nfs-004 created
persistentvolume/pv-nfs-005 created
[root@k8s-master storage]# kubectl apply -f vol-nfs-pvc.yaml
persistentvolumeclaim/nfs-pvc created
pod/pvc-mysql created
[root@k8s-master ~]# kubectl exec -it pvc-mysql -- mysql -u root -pmysql
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| volumes |
+--------------------+
5 rows in set (0.00 sec)


###
测试说明:
如果删除pvc不删除pv,重新创建同样的pvc,那么pvc状态会处于Pending状态,因为pv的当前状态为Released。这也和上面定义的回收策略息息相关。

集群调度

概述

Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:

  • 公平:如何保证每个节点都能被分配资源
  • 资源高效利用:集群所有资源最大化被使用
  • 效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
  • 灵活:允许用户根据自己的需求控制调度的逻辑

Scheduler 是作为单独的程序运行的,启动之后会一直坚挺 API Server,获取 PodSpec.NodeName 为空的pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上

调度过程

调度分为几个部分:首先是过滤掉不满足条件的节点,这个过程称为predicate;然后对通过的节点按照优先级排序,这个是 priority;最后从中选择优先级最高的节点。如果中间任何一步有错误,就直接返回错误
Predicate 有一系列的算法可以使用:

  • PodFitsResources :节点上剩余的资源是否大于 pod 请求的资源
  • PodFitsHost :如果 pod 指定了 NqdeName,检查节点名称是否和 NodeName 匹配
  • PodFitsHostPorts :节点上已经使用的 port 是否和 pod 申请的 port 冲突
  • PodSelectorMatches :过滤掉和 pod 指定的 label 不匹配的节点
  • NoDiskConflict :已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读

如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序

优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:

  • LeastRequestedPriority:通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
  • BalancedResourceAllocation:节点上CPU和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
  • ImageLocalityPriority :倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高

通过算法对所有的优先级项目和权重进行计算,得出最终的结果

节点亲和性

pod.spec.nodeAffinity

  • preferredDuringSchedulinglgnoredDuringExecution:软策略
  • requiredDuringSchedulinglgnoredDuringExecution:硬策略

requiredDuringSchedulinglgnoredDuringExecution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: myapp:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- k8s-node02

# pod 不能放在 k8s-node02

preferredDuringSchedulinglgnoredDuringExecution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: myapp:v1
affinity:
nodeAffinity:
preferredDuringSchedulinglgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: source
operator: In
values:
- qikqiak

键值运算关系

  • In:label的值在某个列表中
  • Notln:label的值不在某个列表中
  • Gt:label的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个label存在
  • DoesNotExist:某个label不存在

Pod亲和性

pod.spec.affinity.podAffinity/podAntiAffinity

  • preferredDuringSchedulinglgnoredDuringExecution:软策略
  • requiredDuringSchedulinglgnoredDuriigExecution:硬策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: v1
kind: Pod
metadata:
name: pod-3
labels :
app: pod-3
spec:
containers:
- name: pod-3
image: myapp:v1
affinity:
podAffinity:
requiredDuringschedulingIgnoredDuringExecution:
- labelselector:
matchExpressions:
- key: app
operator: In
values:
- pod-1
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pod: 2
topologyKey: kubernetes.io/hostname
调度策略 匹配标签 操作符 拓扑域支持 调度目标
nodeAffinity 主机 In,NotIn,Exists,DoesNotExist,Gt,Lt 指定主机
podAffinity POD In,NotIn,Exists,DoesNotExist POD与指定POD同一拓扑域
podAnitAffinity POD In,NotIn,Exists,DoesNotExist POD与指定POD不在同一拓扑域

污点和容忍

节点亲和性,是pod的一种属性(偏好或硬性要求),它使pod被吸引到一类特定的节点。Taint 则相反,它使节点能够排斥一类特定的pod

Taint 和 toleration 相互配合,可以用来避免 pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个taint ,这表示对于那些不能容忍这些 taint的 pod,是不会被该节点接受的。如果将 toleration 应用于 pod上,则表示这些 pod 可以(但不要求)被调度到具有匹配 taint 的节点上

污点Taint

组成

使用kubect1 taint命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去

每个污点的组成如下:key=value:effect

每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用。当前 taint effect 支持如下三个选项:

  • NoSchedule :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上
  • PreferNoSchedule :表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
  • NoExecute :表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去

污点的设置、查看去除

1
2
3
4
5
6
# 设置污点
kubectl taint nodes node1 key1=value1:NoSchedule
# 节点说明中,查找Taints字段
kubectl describe pod pod-name
# 去除污点
kubectl taint nodes node1 key1:NoSchedule-

容忍Tolerations

设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoschedule、NoExecute和 Pod 之间产生互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。 但我们可以在 Pod 上设置容忍(Toleration),意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的Node 上

pod.spec.tolerations

1
2
3
4
5
6
7
8
9
10
11
12
13
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
tolerationSeconds: 3600
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
- key: "key2"
operator: "Exists"
effect: "NoSchedule"
  • 其中 key, vaule, effect 要与 Node 上设置的 taint 保持一致
  • operator 的值为 Exists 将会忽略 value 值
  • tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Pod 上继续保留运行的时间

当不指定 key 值时,表示容忍所有的污点 key:

1
2
tolerations:
- operator: "Exists"

当不指定 effect 值时,表示容忍所有的污点作用

1
2
3
tolerations 
- key: "key"
operator: "Exists"

有多个 Master 存在时,防止资源浪费,可以如下设置

1
kubectl taint nodes Node-Name node-role.kubernetes.io/master=:PreferNoSchedule

固定节点调度

Pod.spec.nodeName 将 Pod 直接调度到指定的 Node 节点上,会跳过 Scheduler 的调度策略,该匹配规则是强制匹配(忽略污点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myweb
spec:
replicas: 7
template:
metadata:
labels:
app: myweb
spec:
nodeName: k8s-node01
containers:
- name: myweb
image: myapp:v1
ports:
- containerPort: 80

Pod.spec.nodeSelector:通过 kubernetes 的label-selector 机制选择节点,由调度器调度策略匹配 label,而后调度 Pod 到目标节点,该匹配规则属于强制约束(不忽略污点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: myweb
spec:
replicas: 2
template:
metadata:
labels :
app: myweb
spec:
nodeSelector:
type: backEndNode1
containers:
- name: myweb
image: harbor/tomcat:8.5-jre8
ports:
- containerPort: 80

安全

Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。API Server 是集群内部各个组件通信的中介,也是外部控制的入口。所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计的。Kubernetes使用了认证(Authentication)、鉴权(Authorization)、准入控制(AdmissionControl)三步来保证API Server的安全

认证

HTTP Token 认证:通过一个 Token 来识别合法用户

  • HTTP Token 的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串-Token 来表达客户的一种方式。Token 是一个很长的很复杂的字符串,每一个 Token 对应一个用户名存储在 API Server 能访问的文件中。当客户端发起 API调用请求时,需要在 HTTP Header 里放入 Token

HTTP Base 认证:通过 用户名+密码 的方式认证

  • 用户名+:+密码 用 BASE64 算法进行编码后的字符串放在 HTTP Request 中的 Heather Authorization 域里发送给服务端,服务端收到后进行编码,获取用户名及密码

最严格的 HITPS 证书认证:基于 CA 根证书签名的客户端身份认证方式(双向)

需要认证的节点

  • Kubenetes 组件对 APl Server 的访问:kubectl、Controller Manager、Scheduler、kubelet、kube-proxy
  • Kubernetes 管理的 Pod 对容器的访问:Pod(dashborad 也是以 Pod 形式运行)

安全性说明

  • Controller Manager、Scheduler与API Server 在同一台机器,所以直接使用 API Server 的非安全端口访问,--insecure-bind-address=127.0.0.1
  • kubectl、kubelet、kube-proxy 访问 APl Server 就都需要证书进行 HTTPS 双向认证

证书颁发

  • 手动签发:通过 k8s 集群的跟 ca 进行签发 HTTPS 证书
  • 自动签发:kubelet 首次访问 API Server 时,使用 token 做认证,通过后,Controller Manager 会为kubelet 生成一个证书,以后的访问都是用证书做认证了

kubeconfig

kubeconfig 文件包含集群参数(CA证书、API Server地址),客户端参数(上面生成的证书和私钥),集群context 信息(集群名称、用户名)。Kubenetes 组件通过启动时指定不同的 kubeconfig 文件可以切换到不同的集群

ServiceAccount

Pod中的容器访问API server。因为Pod的创建、销毁是动态的,所以要为它手动生成证书就不可行了。Kubenetes使用了Service Account解决Pod 访问APl Server的认证问题

Secret与SA的关系

Kubernetes 设计了一种资源对象叫做Secret,分为两类,一种是用于ServiceAccount的service-account-token,另一种是用于保存用户自定义保密信息的 Opaque。ServiceAccount 中用到包含三个部分:Token、ca.crt. namespace

  • token是使用 API Server 私钥签名的JWT。用于访问APl Server时,Server端认证
  • ca.crt,根证书。用于Client端验证API Server发送的证书
  • namespace,标识这个service-account-token的作用域名空间
1
2
kubectl get secret --all-namespaces
kubectl describe secret default-token-5gm9r --namespace=kube-system

默认情况下,每个 namespace 都会有一个 ServiceAccount,如果 Pod 在创建时没有指定 ServiceAccount就会使用 Pod 所属的 namespace 的 ServiceAccount

(默认挂载目录:/run/secrets/kubernetes.io/serviceaccount/)

鉴权

上面认证过程,只是确认通信的双方都确认了对方是可信的,可以相互通信。而鉴权是确定请求方有哪些资源的权限。API Server 目前支持以下几种授权策略(通过 APIServer 的启动参数“-authorization-mode”设置)

  • AlwaysDeny:表示拒绝所有的请求,一般用于测试
  • AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略
  • ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制
  • Webbook:通过调用外部 REST 服务对用户进行授权
  • RBAC(Role-Based Access Control):基于角色的访问控制,现行默认规则

RBAC

RBAC(Role-Based Access Control)基于角色的访问控制。相对其它访问控制方式,拥有以下优势:

  • 对集群中的资源和非资源均拥有完整的覆盖
  • 整个 RBAC 完全由几个 API对象完成,同其它 API 对象一样,可以用 kubectl或 API进行操作
  • 可以在运行时进行调整,无需重启 API Server

RBAC的API资源对象
RBAC引入了4个新的顶级资源对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding,4种对象类型均可以通过 kubectl 与 API操作

需要注意的是 Kubenetes 并不会提供用户管理,那么 User、Group、ServiceAccount 指定的用户又是从哪里来的呢? Kubenetes 组件(kubectl、kube-proxy)或是其他自定义的用户在向 CA 申请证书时,需要提供一个证书请求文件

API Server会把客户端证书的 CN 字段作为User,把 names.0 字段作为Group

kubelet 使用 TLS Bootstaping认证时,API Server 可以使用 Bootstrap Tokens 或者 Token authentication file 验证 =token,无论哪一种,Kubenetes 都会为 token 绑定一个默认的 User 和 Group

Pod使用 ServiceAccount 认证时,service-account-token 中的JWT 会保存 User 信息

有了用户信息,再创建一对角色/角色绑定(集群角色/集群角色绑定)资源对象,就可以完成权限绑定了

Role、ClusterRole

在 RBAC API 中,Role 表示一组规则权限,权限只会增加(累加权限),不存在一个资源一开始就有很多权限而通过 RBAC 对其进行减少的操作;Role 可以定义在一个 namespace 中,如果想要跨 namespace 则可以创建ClusterRole

1
2
3
4
5
6
7
8
9
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-reader
rules :
- apiGroups:[""] # "" indicates the core ApI group
resources: ["pods"]
verbs: ["get", "watch", "list"]

ClusterRole 具有与 Role 相同的权限角色控制能力,不同的是 ClusterRole 是集群级别的,ClusterRole 可以用于:

  • 集群级别的资源控制( 例如 node 访问权限)
  • 非资源型 endpoints( 例如 /healthz 访问)
  • 所有命名空间资源控制(例如 pods)
1
2
3
4
5
6
7
8
9
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: secret-reader
rules :
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]

RoleBinding、ClusterRoleBinding

RoleBinding 可以将角色中定义的权限授予用户或用户组,RoleBinding包含一组权限列表(subjects),权限列表中包含有不同形式的待授予权限资源类型(users,groups,or service accounts);RoleBinding同样包含对被 Bind 的 Role 引用:RoleBinding,适用于某个命名空间内授权,而 ClusterRoleBinding,适用于集群范围内的授权
将 defaut 命名空间的 pod-reader Role 授予jane 用户,此后jane 用户在 default 命名空间中将具有 pod-reader 的权限

1
2
3
4
5
6
7
8
9
10
11
12
13
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io

RoleBinding 同样可以引用 ClusterRole 来对当前 namespace 内用户、用户组或 ServiceAccount 进行授权,这种操作允许集群管理员在整个集群内定义一些通用的 ClusterRole,然后在不同的 namespace 中使用RoleBinding 来引用
例如,以下 RoleBinding引用了一个 ClusterRole,这个 ClusterRole 具有整个集群内对 secrets 的访问权限;但是其授权用户“dave“只能访问 development 空间中的 secrets(因为 RoleBinding 定义在development 命名空间)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# This role binding allows "dave" to read secrets in the "development" namespace.
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-secrets
namespace: development # This only grants permissions within the "development" namespace.
subjects:
- kind: User
name: dave
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io

使用 ClusterRoleBinding可以对整个集群中的所有命名空间资源权限进行授权;以下ClusterRoleBinding示例展示了授权 manager 组内所有用户在全部命名空间中对 secrets 进行访问

1
2
3
4
5
6
7
8
9
10
11
12
13
# This cluster role binding allows anyone in the "manager" group to read secrets in any namespace.
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-secrets-global
subjects :
- kind: Group
name: manager
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io

Resources

Kubernetes 集群内一些资源一般以其名称字符串来表示,这些字符串一般会在 API的 URL 地址中出现;同时某些资源也会包含子资源,例如 logs 资源就属于 pods 的子资源,API 中 URL 样例如下

1
GET /api/v1/namespaces/{namespace}/pods/{name}/log

如果要在 RBAC 授权模型中控制这些子资源的访问权限,可以通过/分隔符来实现,以下是一个定义 pods 资资源 logs 访问权限的 Role

1
2
3
4
5
6
7
8
9
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]

to Subjects

RoleBinding和 ClusterRoleBinding可以将 Role 绑定到 Subjects;Subjects 可以是groups、users,或者service accounts
Subjects 中 Users 使用字符串表示,它可以是一个普通的名字字符串,如“alice”;也可以是email 格式的邮箱地址;甚至是一组字符串形式的数字 ID。但是 Users 的前缀 system: 是系统保留的,集群管理员应该确保普通用户不会使用这个前缀格式
Groups 书写格式与 Users 相同,都为一个字符串,并且没有特定的格式要求;同样 system: 前缀为系统保留

创建一个用户只能管理dev空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
"CN": "devuser",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}

# 下载证书生成工具
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
mv cfssl_linux-amd64 /usr/local/bin/cfssl

wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson

wget https://pkg.cfssl.org/R1.2/cfss1-certinfo_linux-amd64
mv cfssl-certinfo_linux-amd64 /usr/local/bin/cfssl-certinfo

cfssl gencert -ca=ca.crt -ca-key=ca.key -profile=kubernetes /root/devuser-csr.json |
cfssljson -bare devuser

# 设置集群参数
export KUBE_APISERVER="https://172.20.0.113:6443"
kubectl config set-cluster kubernetes --certificate-authority=/etc/kubernetes/ssl/ca.pem --embed-certs=true
--server=${KUBE APISERVER} --kubeconfig=devuser.kubeconfig

# 设置客户端认证参数
kubectl config set-credentials devuser --client-certificate=/etc/kubernetes/ssl/devuser.pem --client-key=/etc/kubernetes/ssl/devuser-key.pem --embed-certs=true --kubeconfig=devuser.kubeconfig

# 设置上下文参数
kubectl config set-context kubernetes --cluster=kubernetes--user=devuser --namespace=dev --kubeconfig=devuser.kubeconfig

# 设置默认上下文
kubectl config use-context kubernetes --kubeconfig=devuser.kubeconfig

cp -f ./devuser.kubeconfig /root/.kube/config

kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev

准入控制

准入控制是API Server的插件集合,通过添加不同的插件,实现额外的准入控制规则。甚至于API Server的一些主要的功能都需要通过 Admission Controllers 实现,比如 ServiceAccount

列举几个插件的功能:

  • NamespaceLifecycle:防止在不存在的namespace 上创建对象,防止删除系统预置namespace,删除namespace 时,连带删除它的所有资源对象
  • LimitRanger:确保请求的资源不会超过资源所在 Namespace 的 LimitRange 的限制
  • ServiceAccount:实现了自动化添加ServiceAccount
  • ResourceQuota:确保请求的资源不会超过资源的 ResourceQuota 限制

Helm

在没使用 helm 之前,向 kubernetes 部署应用,我们要依次部署 deployment、svc 等,步骤较繁琐。况且随着很多项目微服务化,复杂的应用在容器中部署以及管理显得较为复杂,helm 通过打包的方式,支持发布的版本管理和控制,很大程度上简化了 Kubernetes 应用的部署和管理
Helm 本质就是让 K8s的应用管理(Deployment,Service 等)可配置,能动态生成。通过动态生成 K8s 资源清单文件(deployment.yaml,service.yaml)。然后调用 Kubectl 自动执行 K8s 资源部署
Helm 是官方提供的类似于 YUM 的包管理器,是部署环境的流程封装。Helm 有两个重要的概念:chart 和release

  • chart 是创建一个应用的信息集合,包括各种 Kubernetes 对象的配置模板、参数定义、依赖关系、文档说明等。chart 是应用部署的自包含逻辑单元。可以将 chart 想象成 apt、yum 中的软件安装包
  • release 是chart 的运行实例,代表了一个正在运行的应用。当 chart 被安装到 Kubernetes 集群,就生成一个 release。chart 能够多次安装到同一个集群,每次安装都是一个 release

Helm

证书可用年限修改

高可用的k8s构建

  • HAProxy:作为负载均衡器,用来分发流量到多个 kube-apiserver 实例
  • Keepalived:为 Kubernetes 控制平面提供虚拟 IP(VIP),确保在 Master 节点发生故障时能够自动切换到健康的节点
  • etcd 集群:Kubernetes 使用 etcd 存储集群状态数据,需要为 etcd 配置高可用性
  • Cloud Provider / MetalLB(如果在裸机环境中):为外部访问提供负载均衡器或静态 IP

高可用的 API Server 部署

Kubernetes API Server 是所有集群操作的入口,因此必须保证它的高可用性。由于 kube-apiserver 是 stateless 的,我们可以使用负载均衡器将请求分发到多个 kube-apiserver 实例

配置 HAProxy 或 Nginx 作为负载均衡器:

  • 在三个 Master 节点中部署 HAProxy 或 Nginx 负载均衡器
  • 配置负载均衡器将流量转发到每个 kube-apiserver 实例

配置 Keepalived 提供虚拟 IP (VIP):

使用 Keepalived 来配置一个虚拟 IP,使得 Master 节点之间能够在故障时进行切换

  • 在三个 Master 节点上配置 Keepalived
  • 配置一个 VIP 地址,所有 API 请求都会通过这个 VIP 进行访问