LEMON

记录站

0%

Kubernetes

一、K8S概念

1. 什么是Kubernetes ?

k8s是一组服务器集群,K8s所管理的集群节点上的容器。官方网站

Kubernetes是一个可移植的,可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。它拥有一个庞大且快速增长的生态系统。Kubernetes的服务,支持和工具广泛可用。

Kubernetes这个名字起源于希腊语,意思是舵手或飞行员。Google在2014年开源了Kubernetes项目。Kubernetes将超过15年的Google在大规模生产工作负载方面的经验与社区中最好的想法和实践相结合。

c

2. 为什么需要它?他的功能?

容器是捆绑和运行应用程序的好方法。在生产环境中,您需要管理运行应用程序的容器,并确保没有停机时间。例如,如果一个容器发生故障,则需要启动另一个容器。如果由系统处理此行为,会不会更容易?

这就是Kubernetes的救援方法!Kubernetes为您提供了一个可弹性运行分布式系统的框架。它负责应用程序的扩展和故障转移,提供部署模式等。例如,Kubernetes可以轻松管理系统的Canary部署。

自我修复

在节点故障时重新启动失败的容器,替换和重新部署容器,保证预期的副本数量;杀死健康检查失败的容器,并且在未准备好之前不会处理客户端请求,确保线上服务不中断。

弹性伸缩

使用命令、UI或者基于CPU使用情况自动快速扩容和缩容应用程序实例,保证应用业务高峰并发时的高可用性;业务低峰时回收资源,以最小成本运行服务。

自动部署和回滚

Kubernetes采用滚动更新策略更新应用,一次更新一个Pod,而不是同时删除所有Pod,如果更新过程中出现问题,Kubernetes将回滚更改,升级保证业务不受影响。

存储编排

挂载外部存储系统,无论是来自本地存储,公有云(如AWS),还是网络存储(如NFS、iSCSI、GlusterFS、Ceph)都作为集群资源的一部分使用,极大提高存储使用灵活性。

服务发现和负载均衡

Kubernetes为多个容器提供一个统一访问入口(内部IP地址和一个DNS名称),并且负载均衡关联的所有容器,使得用户无需考虑容器IP问题。集群内应用可以通过DNS名称访问另一个应用,方便微服务之间通信。

机密和配置管理

管理机密数据和应用程序配置,而不需要把敏感数据暴露在镜像里,提高敏感数据安全性。

资源监控

Node节点组件集成cAdvisor资源收集工具,可通过Heapster汇总整个集群节点资源数据,然后存储到InfluxDB时序数据库,再由Grafana展示,可以快速实现对集群资源监控,满足基本监控需求。

提供认证和授权

支持属性访问控制 (ABAC)、角色访问控制(RBAC)认证授权策略,控制用户是否有权限使用Kubernetes API做某些事情,精细化权限分配。

3. Kubernetes架构

kubernetes分别有两种角色:1、master 管理节点 2、worker 工作节点

b

生产k8s集群图构

a

4. Kubernetes组件

kubernetes分为 Master节点和 Node节点,前者是管理节点,后者是容器运行的节点。其中Master节点主要有3个重要组件,分别是APIServer,sheduler 和 controller manager。

Node节点 有两个组件 kubelet 和 kubelet (有时候master节点也可以既是管理节点也是工作节点)

4.1 Master 组件

API Server

APIServer组件负责响应用户的管理请求、进行指挥协调工作,所有服务访问统一入口。

Scheduler

scheduler组件是将待调度的pod按照一定的调度算法绑定到合适的工作节点上,负责介绍任务,选择合适的点进行分配任务。

Controller manager

是一组控制器的合集,负责控制控制管理对应的资源,如副本(replication)和工作节点(node)等。维持副本期望数目

ETCD

etcd 负责以K/V的形式保存 Kubernetes Cluster 的配置信息和各种资源的状态信息。当数据发生变化时,etcd 会快速地通知 Kubernetes 相关组件。键值对数据库储存K8S集群所有重要信息(持久化)

4.2 Node组件

管理维护pod运行的agent,直接跟容器引擎交互实现容器的生命周期管理

Kube-proxy

将service的流量转发到对应endpoint,负责写入规则至 IPTABLES、IPvs实现服务映射访间的

Flannel网络

维持各个节点上pod之间的通信。

4.3 重要组件

Coredns

可以为集群中的Svc创建一个域名IP的对应关系解析

Dashboard

给K8S集群提供一个B/S结构访问体系

Ingress Controller

官方只能实现四层代理, INGRESS可以实现七层代理

Federation

提供一个可以跨集群中心多K8S统一管理功能

Prometheus

提供K8sS集群的监控能力

ELK

提供K8s集群旦志统一分析介入平台

5. k8s网络架构概念

CNI(容器网络接口)

CNI是Container Network Interface的是一个标准的,通用的接口。现在容器平台:docker,kubernetes,mesos,容器网络解决方案:flannel,calico,weave。只要提供一个标准的接口,就能为同样满足该协议的所有容器平台提供网络功能,而CNI正是这样的一个标准接口协议。

pod的通信分类

同一个Pod内的多个容器之间:lo Network

各Pod之间 或 不同主机各Pod 的通讯:Over lay Network

Pod 与 Service之间的通讯:各节点的 Iptables规则 或者 IPVS

K8S整体网络模型图

K8S中的三种网络

node网络:负责不同主机之间的pod通信的入口和出口(物理网络)

service网络:负责对外提供服务(暴露网络)

pod网络:负责pod内部容器之间的通信 和 pod 与 pod之间的通信(内部网络)

image-20200920141256555

K8S的网络通讯原理

同一个Pod内部通讯:

  • 同一个Pod共享同一个网络命名空间,共享同一个 Linux协议栈。

Pod1 至 Pod2:

  • Pod1至Pod2不在同一台主机:Pod的地址是与 docker0在同一个网段的,但 docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将PoIP和所在 Node’MJIP关联起来,通过这个关联让Pod可以互相访问
  • Podl与Pod2在同一台机器:由 Docker0网桥直接转发请求至Pod2,不需要经过 Flannel

Pod 至 Service的网络:

  • iptables 或者 IPVS维护和转发

Pod到外网:

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

外网访问Pod:

  • 访问对应的Service即可

ees

6. Kubernetes核心概念

6.1 container

容器可以运行服务和程序,容器是独立运行的一个或一组应用。容器可以进行启动、开始、停止、删除等操作,每个容器都是相互隔离的。可以把容器看作是一个简易版的linux环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行其中的应用程序。

6.2 namespace

可以将一个物理的cluster逻辑上划分成多个虚拟cluster,每个cluster就是一个namespace。不同的namespace里的资源是完全隔离的。

6.3 labels

标签用于区分对象(比如pod和services), kubernetes中的任意对象都是通过label进行标识,label的实质是一系列的key/value键值对,其中key与value由用户自己指定。label可以附加到各种资源对象上,如node、pod、service、RC等,一个资源对象可以定义任意数量的label,可以通过label selector(标签选择器)查询和筛选资源对象。label是rc和service运行的基础,两者通过label来进行关联node上运行的pod

6.4 pod

在kubernetes系统中, pod是最小部署单元, 一个pod包含一个或多个容器(一组容器的集合), pod 是一个可以被创建、销毁、调度、管理的最小部署单元。kubernates为每个pod都分配了唯一的ip地址, 称之为PodIP, 一个pod里的多个容器共享podip地址,它负责外部跟容器之间进行通信。

Pod主要分为两种类型:1、自主式pod,2、控制器管理pod

pod是kubernetes最重要的基本概念,也是k8s中的最小运行单元,一个pod中可以运行一个或多个的container及一个管理 container——pause

image

6.5 Controller Manager

Controller Manager 由 kube-controller-manager 和 cloud-controller-manager 组成, 是Kubernetes 的大脑, 它通过 apiserver 监控整个集群的状态, 并确保集群处于预期的工作状态。

image-20210226110055294

6.6 services

services 是kubernetes最外围的单元,通过虚拟一个访问ip及服务端口,可以访问我们定义好的pod资源,是通过iptables的nat转发来实现,转发的目标端口为kube_proxy生成的随机端口。services代理pod集合对外表现是一个访问入口,分配一个集群ip地址,来自这个ip的请求将负载均衡转发后端pod中的容器,service通过lable selector选择一组pod提供服务。

deployment可以部署多个副本,每个pod 都有自己的IP,外界如何访问这些副本那?

答案是:service

k8s的 service定义了外界访问一组特定pod的方式。service有自己的IP和端口,service为pod提供了负载均衡。

k8s运行容器pod与访问容器这两项任务分别由controller和service执行。

image-20200920132730652

K8S-Service类型:

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

NodePort:在ClusterIP基础上为Service在每台机器上绑定一个端口,这样可以通过NodeIP:NodePort来访问服务

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

二、部署K8S集群(kubeadm)

准备三台Linux虚拟机(K8S集群三台起步),系统用CentOS7.4,虚拟机配置是2颗CPU和2G内存(K8S最低要求的配置),网络使用桥接网卡方式并使用静态IP

主机名 IP地址 角色描述
Master ens32:192.168.2.1 K8S Master节点/ETCD节点
Node1 ens32:192.168.2.2 K8S Node节点
Node2 ens32:192.168.2.3 K8S Node节点
harbor ens32:192.168.2.4 docker 镜像仓库节点

1. 系统环境初始化

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
# 修改主机名
hostnamectl set-hostname master1
hostnamectl set-hostname node1
hostnamectl set-hostname node2
hostnamectl set-hostname harbor
logout

# 设置系统时区为中国/上海
timedatectl set-timezone Asia/Shanghai

# 将当前的 UTC 时间写入硬件时钟
timedatectl set-local-rtc 0

# 重启依赖于系统时间的服务
systemctl restart rsyslog
systemctl restart crond


# 下载阿里源
wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo


# 安装相关依赖
yum install -y epel-release conntrack ntpdate ntp ipvsadm ipset iptables-services iptables curl sysstat libseccomp wget unzip net-tools git yum-utils jq device-mapper-persistent-data lvm2


# 同步时间
ntpdate ntp1.aliyun.com


# 添加hosts
cat <<EOF>> /etc/hosts
192.168.2.1 master1
192.168.2.2 node1
192.168.2.3 node2
192.168.2.4 hub.lemon.com
199.232.68.133 raw.githubusercontent.com
EOF

# 设置防火墙为 Iptables 并设置空规则
systemctl stop firewalld
systemctl disable firewalld
systemctl start iptables
systemctl enable iptables
iptables -F && service iptables save


# 关闭swap、selinux
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

setenforce 0
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

# 关闭系统不需要服务
systemctl stop postfix && systemctl disable postfix


# 设置 rsyslogd 和 systemd journald
# 持久化保存日志的目录
mkdir /var/log/journal
mkdir /etc/systemd/journald.conf.d

cat > /etc/systemd/journald.conf.d/99-prophet.conf <<EOF
[Journal]
# 持久化保存到磁盘
Storage=persistent

# 压缩历史日志
Compress=yes

SyncIntervalSec=5m
RateLimitInterval=30s
RateLimitBurst=1000

# 最大占用空间 10G
SystemMaxUse=10G

# 单日志文件最大 200
MSystemMaxFileSize=200M

# 日志保存时间 2 周
MaxRetentionSec=2week

# 不将日志转发到 syslog
ForwardToSyslog=no
EOF

systemctl restart systemd-journald

2. 升级系统内核并优化

CentOS 7.x 系统自带的 3.10.x 内核存在一些 Bugs,导致运行的 Docker、Kubernetes 不稳定,例如:

高版本的 docker(1.13 以后) 启用了 3.10 kernel 实验支持的 kernel memory account 功能(无法关闭),当节点压力大如频繁启动和停止容器时会导致 cgroup memory leak;网络设备引用计数泄漏, 会导致类似于报错:”kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1”;

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
# 解决方案:升级内核到 4.4.X 以上;

# 下载内核源
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

# 安装最新版本内核
yum --enablerepo=elrepo-kernel install -y kernel-lt

# 查看可用内核
cat /boot/grub2/grub.cfg |grep menuentry

# 查看内核启动项
grub2-editenv list

# 升级系统文件
yum update -y

# # 设置开机从新内核启动 & 重启系统
grub2-set-default "CentOS Linux (4.4.236-1.el7.elrepo.x86_64) 7 (Core)" && reboot

# 查看内核
uname -r


# kube-proxy开启ipvs的前置条件
modprobe br_netfilter

cat > /etc/sysconfig/modules/ipvs.modules <<EOF
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF

chmod 755 /etc/sysconfig/modules/ipvs.modules

bash /etc/sysconfig/modules/ipvs.modules

lsmod | grep -e ip_vs -e nf_conntrack_ipv4

# 开机自加载
chmod a+x /etc/rc.d/rc.local
echo 'bash /etc/sysconfig/modules/ipvs.modules' >> /etc/rc.local


# 优化内核参数
cat > /etc/sysctl.d/kubernetes.conf <<EOF
# 关闭IPV6协议
net.ipv6.conf.all.disable_ipv6=1

# 开启网桥模式
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1

# 开启路由转发
net.ipv4.ip_forward=1

net.ipv4.tcp_tw_recycle=0

# 禁止使用 swap 空间,只有当系统 OOM 时才允许使用它
vm.swappiness=0

# 不检查物理内存是否够用
vm.overcommit_memory=1

# 开启 OOM
vm.panic_on_oom=0

fs.inotify.max_user_watches=1048576
fs.inotify.max_user_instances=8192

# 开启的文件句柄数目
fs.file-max=52706963

# 开启对大的文件数目
fs.nr_open=52706963

# ~~~
net.netfilter.nf_conntrack_max=2310720
vm.dirty_bytes=15728640
EOF

# 开机自动加载
sysctl -p /etc/sysctl.d/kubernetes.conf

3. 部署Docker、kubeadm、kubectl

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
# 下载阿里的docker源、Centos7源、kubernetes源
cd /etc/yum.repos.d/

wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

cat <<EOF>> kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF

yum clean all && yum makecache && cd


# 安装docke,记给docker换镜像源
yum -y install docker-ce-18.09.6

# 创建 /etc/docker 目录
mkdir -p /etc/docker

# 配置 daemon配置文件
cat > /etc/docker/daemon.json <<EOF
{
"registry-mirrors": ["https://p8hkkij9.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF

#启动docker
systemctl start docker
systemctl enable docker


# 安装 Kubeadm (主从配置)
# 这里正常情况下,master上安装kubeadm、kubectl,node只安装kubelet就行,不过都装上也没事。
yum -y install kubeadm-1.15.1 kubectl-1.15.1 kubelet-1.15.1

# 设置开机启动kubelet
systemctl enable kubelet

4. 初始化主节点

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
# k8s节点上提前下载k8s镜像
cat image-k8s-v1_15_1.sh
#/bin/bash
images=(
kube-apiserver:v1.15.1
kube-controller-manager:v1.15.1
kube-scheduler:v1.15.1
kube-proxy:v1.15.1
pause:3.1
etcd:3.3.10
coredns:1.3.1
)

for imageName in ${images[@]} ; do
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName k8s.gcr.io/$imageName
docker rmi registry.cn-hangzhou.aliyuncs.com/google_containers/$imageName
done

# 执行脚本
bash image-k8s-v1_15_1.sh

# 检查镜像是否完善
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
k8s.gcr.io/kube-apiserver v1.15.1 68c3eb07bfc3 14 months ago 207MB
k8s.gcr.io/kube-controller-manager v1.15.1 d75082f1d121 14 months ago 159MB
k8s.gcr.io/kube-scheduler v1.15.1 b0b3c4c404da 14 months ago 81.1MB
k8s.gcr.io/kube-proxy v1.15.1 89a062da739d 14 months ago 82.4MB
k8s.gcr.io/coredns 1.3.1 eb516548c180 20 months ago 40.3MB
k8s.gcr.io/etcd 3.3.10 2c4adeb21b4f 21 months ago 258MB
k8s.gcr.io/pause 3.1 da86e6ba6ca1 2 years ago 742kB


# 修改kubeadm默认初始化配置模板
[root@master1 ~]# kubeadm config print init-defaults > kubeadm-config.yaml

[root@master1 ~]# cat kubeadm-config.yaml
apiVersion: kubeadm.k8s.io/v1beta2
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
# 填写好master1的IP地址
advertiseAddress: 192.168.2.1
bindPort: 6443
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta2
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns:
type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: k8s.gcr.io
kind: ClusterConfiguration
# 选择初始化的k8s集群版本镜像
kubernetesVersion: v1.15.1
networking:
dnsDomain: cluster.local
# 原因是一会要用的flannel来解决pod的扁平化网络,而flannel默认的网段就是10.244.0.0/16,所以这就将pod的IP段设置为和flannel相同的IP段,免得后期再做修改
podSubnet: "10.244.0.0/16"
serviceSubnet: 10.96.0.0/12
scheduler: {}
# 下面这一段的意思是将默认的iptables调度方式改为为IPVS
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
featureGates:
SupportIPVSProxyMode: true
mode: ipvs


# 初始化k8s集群
# 如果
# --experimental-upload-certs:可以让后来加入的主节点自动加入证书;注:v1.15以上版本的参数已改为--upload-certs
[root@master1 ~]# kubeadm init --config=kubeadm-config.yaml --experimental-upload-certs | tee kubeadm-init.log

5. 加入主节点以及其余工作节点

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@master1 ~]# cat kubeadm-init.log
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

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

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.2.1:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:4dc6790b3232bc4fd7f80e614d38e21b15cfd3d318f09b596b68336375b381e4


# 检查kubelet服务是否已经打开
systemctl status kubelet | grep running
Active: active (running) since 日 2020-09-13 01:53:32 CST; 6min ago


# 但是会发现节点的状态是NotReady
[root@master1 ~]# kubectl get node
NAME STATUS ROLES AGE VERSION
master1 NotReady master 11m v1.15.1
node1 NotReady <none> 2m13s v1.15.1
node2 NotReady <none> 2m10s v1.15.1

这就需要下面来部署flannel来解决扁平化网络问题

6. 部署 CNI - flannel网络

1
2
3
4
5
6
7
8
9
# 部署flannel全覆盖网络
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

# 再来检查节点状态
[root@master1 ~]# kubectl get node
NAME STATUS ROLES AGE VERSION
master1 Ready master 18m v1.15.1
node1 Ready <none> 8m38s v1.15.1
node2 Ready <none> 8m35s v1.15.1+

7. 查看集群状态 和相关的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master1 ~]# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
master1 Ready master 20m v1.15.1 192.168.2.1 <none> CentOS Linux 7 (Core) 4.4.236-1.el7.elrepo.x86_64 docker://18.9.6
node1 Ready <none> 10m v1.15.1 192.168.2.2 <none> CentOS Linux 7 (Core) 4.4.236-1.el7.elrepo.x86_64 docker://18.9.6
node2 Ready <none> 10m v1.15.1 192.168.2.3 <none> CentOS Linux 7 (Core) 4.4.236-1.el7.elrepo.x86_64 docker://18.9.6

[root@master1 ~]# kubectl get pod --all-namespaces -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system coredns-5c98db65d4-57czz 1/1 Running 4 136d 10.244.0.9 master1 <none> <none>
kube-system coredns-5c98db65d4-s9fsm 1/1 Running 4 136d 10.244.0.8 master1 <none> <none>
kube-system etcd-master1 1/1 Running 3 136d 192.168.2.1 master1 <none> <none>
kube-system kube-apiserver-master1 1/1 Running 3 136d 192.168.2.1 master1 <none> <none>
kube-system kube-controller-manager-master1 1/1 Running 3 136d 192.168.2.1 master1 <none> <none>
kube-system kube-flannel-ds-amd64-6qw52 1/1 Running 20 136d 192.168.2.3 node2 <none> <none>
kube-system kube-flannel-ds-amd64-hj7wr 1/1 Running 1 136d 192.168.2.1 master1 <none> <none>
kube-system kube-flannel-ds-amd64-wc96r 1/1 Running 20 136d 192.168.2.2 node1 <none> <none>
kube-system kube-proxy-5xcpl 1/1 Running 5 136d 192.168.2.3 node2 <none> <none>
kube-system kube-proxy-lwjps 1/1 Running 4 136d 192.168.2.1 master1 <none> <none>
kube-system kube-proxy-r77pb 1/1 Running 4 136d 192.168.2.2 node1 <none> <none>
kube-system kube-scheduler-master1 1/1 Running 3 136d 192.168.2.1 master1 <none> <none>

8. 搭建配置harbor私有仓库

安装Harbor需要先安装docker和docker-compose,上面的系统初始化、系统升级和优化、安装Docker的步骤这里不再陈述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 在所有docker节点的daemon.json文件上添加下面信任配置
cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://p8hkkij9.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"],
"insecure-registries": ["https://hub.lemon.com"]
}
#~~ 这里先不急着重启docker,等一会颁发完证书之后在重启


# 在Harbor节点上安装docker-compose
[root@harbor ~]# curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
[root@harbor ~]# chmod a+x docker-compose
[root@harbor ~]# mv docker-compose /usr/local/bin/
[root@harbor ~]# docker-compose --version
docker-compose version 1.23.2, build 1110ad01


# 安装Harbor私有hub,创建CA证书
[root@harbor ~]# which openssl
/usr/bin/openssl
[root@harbor ~]# mkdir -p /data/ssl && cd /data/ssl/
[root@harbor ssl]# openssl req -newkey rsa:4096 -nodes -sha256 -keyout ca.key -x509 -days 365 -out ca.crt

1
2
# 创建生成证书签名请求
[root@harbor ssl]# openssl req -newkey rsa:4096 -nodes -sha256 -keyout hub.lemon.com.key -out hub.lemon.com.csr

1
2
3
4
5
# 生成注册表主机证书
[root@harbor ssl]# openssl x509 -req -days 365 -in hub.lemon.com.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out hub.lemon.com.crt
Signature ok
subject=/C=CN/ST=Beijing/L=Beijing/O=lemon/OU=hub/CN=hub.lemon.com
Getting CA Private Key
1
# 查看生成的证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 信任自签发的域名证书
# 由于linux操作系统不信任自签发的CA证书,所以需要把证书加入到系统的信任证书里
# 添加自签证书到系统
[root@harbor ssl]# cp hub.lemon.com.crt /etc/pki/ca-trust/source/anchors/
[root@harbor ssl]# ls -lh /etc/pki/ca-trust/source/anchors/
总用量 4.0K
-rw-r--r-- 1 root root 1.9K 9月 13 02:23 hub.lemon.com.crt

# 让系统CA信任立刻生效
[root@harbor ssl]# update-ca-trust enable
[root@harbor ssl]# update-ca-trust extract

# 如果已经启动Docker了,必须要重启;如果安装过Harbor以后再重启的话,有可能会出现harbor连不上的情况,需要重新把Harbor启动的容器和镜像删除后,重新install一遍
[root@harbor ssl]# systemctl restart docker

# 创建harbor的证.目录
[root@harbor ssl]# mkdir -p /usr/local/harbor/ssh

# 复制域名证到harbor要安装的路径
[root@harbor ssl]# cp hub.lemon.com.crt hub.lemon.com.key /usr/local/harbor/ssh/

# 安装测试,上传harbor安装包并解压到相应路径

1
2
3
[root@harbor ~]# tar xf harbor-offline-installer-v1.9.0.tgz
[root@harbor ~]# mv harbor/* /usr/local/harbor/
[root@harbor ~]# cd /usr/local/harbor/ && ls

1
2
3
# harbor配置备份 & 修改配置文件
[root@harbor harbor]# cp harbor.yml harbor.yml.bak
[root@harbor harbor]# vim harbor.yml

gggg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 生成配置 & 安装(需联网)
# 下载harbor所需镜像
[root@harbor harbor]# ./prepare
[root@harbor harbor]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
goharbor/prepare v1.9.0 aa594772c1e8 12 months ago 147MB

# 启动Harbor
[root@harbor harbor]# ./install.sh --with-notary --with-clair --with-chartmuseum

# Harbor日志文件存放路径为/var/log/harbor/
# 如果需要修改Harbor的配置文件harbor.yml,因为Harbor是基于docker-compose服务编排的,我们可以使用docker-compose命令重启Harbor。不修改配置文件,重启Harbor命令:docker-compose start | stop | restart
1、停止Harbor
[root@harbor ~]# docker-compose -f /usr/local/harbor/docker-compose.yml down

2、启动Harbor
[root@harbor ~]# docker-compose -f /usr/local/harbor/docker-compose.yml up -d

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 开机自运行
[root@harbor ~]# cat <<END>> /etc/rc.local
docker-compose -f /usr/local/harbor/docker-compose.yml up -d
END

[root@harbor ~]# chmod u+x /etc/rc.d/rc.local

# 登录Harbor仓库
[root@harbor harbor]# docker login https://hub.lemon.com
Username: admin
Password: Harbor12345
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

# 客户端测试是否能够访问(须在客户端加入hosts)
https://hub.lemon.com/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 随便在一个节点上测试docker是否能使用harbor库
[root@master1 ~]# docker login https://hub.lemon.com
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

[root@master1 ~]# docker pull httpd
[root@master1 ~]# docker tag httpd:latest hub.lemon.com/library/httpd:v1 #打好标签
[root@master1 ~]# docker rmi httpd:latest
Untagged: httpd:latest
Untagged: httpd@sha256:0fce91cc167634ede639701b7dd1d8093f4ad2f2d9d0d5a8f4be2eaef8a570fb

1
2
3
4
5
6
7
8
9
[root@master1 ~]# docker push hub.lemon.com/library/httpd:v1    #推送至harbor仓库
The push refers to repository [hub.lemon.com/library/httpd]
f1fee4547086: Pushed
a6a46c0268b1: Pushed
951b1be5cf2d: Pushed
d37da03a9458: Pushed
07cab4339852: Pushed
v1: digest: sha256:8c9bc11ca46ffd0b6b8a00e30aa670abef6c0d5d308e318a5cb8cf9e23931649 size: 1367
# 回到浏览器查看

iiii

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
# Kubernetes拉取harbor库镜像创建pod
# 在此之前先将打标签的镜像删除
[root@master1 ~]# docker rmi hub.lemon.com/library/httpd:v1
Untagged: hub.lemon.com/library/httpd:v1
Untagged: hub.lemon.com/library/httpd@sha256:8c9bc11ca46ffd0b6b8a00e30aa670abef6c0d5d308e318a5cb8cf9e23931649
Deleted: sha256:6d82971d37d087be9917ab2015a4dc807569c736d3f2017c0821ddc4ed126617
Deleted: sha256:59a897aaa844713f078ea9234bd61b0f4885598a9ffb1267b4c59983813abb52
Deleted: sha256:6942605f2c5a8ba622491e369f2585daafe749a645835f5abb4fb9d11803664d
Deleted: sha256:0f44970c8ecb7e1107f45ff7d5a7f7f3799a9821dce5cd30c51f2f7641339665
Deleted: sha256:97635989e45ed57deef09cd09be52d008a073f2e1e045a1ba91956fbc2db2961
Deleted: sha256:07cab433985205f29909739f511777a810f4a9aff486355b71308bb654cdc868

# 基于harbor仓库的镜像启动Pod
[root@master1 ~]# kubectl run httpd-01 --image=hub.lemon.com/library/httpd:v1 --port=80 --replicas=1
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/httpd-01 created

# 查看所有deployment
[root@master1 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
httpd-01 1/1 1 1 21s

# 查看所有rs(RESTARTS副本)
[root@master1 ~]# kubectl get rs
NAME DESIRED CURRENT READY AGE
httpd-01-6c9fbcfb65 1 1 1 47s

# 查看所有pod
[root@master1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
httpd-01-6c9fbcfb65-jvmc8 1/1 Running 0 58s
[root@master1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-01-6c9fbcfb65-jvmc8 1/1 Running 0 69s 10.244.1.2 node1 <none> <none>

# 交互式执行容器命令
[root@master1 ~]# kubectl exec -it httpd-01-6c9fbcfb65-9hlhv ls
bin build cgi-bin conf error htdocs icons include logs modules

# 访问这个pod的IP
[root@master1 ~]# curl 10.244.1.2
<html><body><h1>It works!</h1></body></html>

# 删除所有已经退出的容器
docker rm -v $(docker ps -qa -f status=exited)

k

9. 基本的使用下K8S

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# 查看pod的详细信息
[root@master1 ~]# kubectl -n default get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-01-6c9fbcfb65-jvmc8 1/1 Running 1 12h 10.244.1.3 node1 <none> <none>

# 测试删除这个pod容器后,k8s会不会重新策划这个pod
kubectl -n default delete pod httpd-01-6c9fbcfb65-jvmc8

# 验证, 可以看到k8s看到副本的期望值不符合之后,就会马上新起来一个pod来满足这个期望值
[root@master1 ~]# kubectl -n default get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-01-6c9fbcfb65-9hlhv 1/1 Running 1 12h 10.244.1.3 node1 <none> <none>


# 在生产环境下发现一个副本的pod已经不够用了,需要扩容
[root@master1 ~]# kubectl -n default scale --replicas=3 deployment/httpd-01
deployment.extensions/httpd-01 scaled

[root@master1 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
httpd-01 3/3 3 3 12h

[root@master1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
httpd-01-6c9fbcfb65-9hlhv 1/1 Running 1 12h
httpd-01-6c9fbcfb65-hjhjn 1/1 Running 0 51s
httpd-01-6c9fbcfb65-x52hh 1/1 Running 0 51s

[root@master1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-01-6c9fbcfb65-9hlhv 1/1 Running 1 12h 10.244.1.3 node1 <none> <none>
httpd-01-6c9fbcfb65-hjhjn 1/1 Running 0 2m8s 10.244.2.3 node2 <none> <none>
httpd-01-6c9fbcfb65-x52hh 1/1 Running 0 2m8s 10.244.2.2 node2 <none> <none>
# 能够看到,已经扩容成功了,没错,就是这么简单~~


# 但是现在又引来了一个新的问题,我有三个容器,端口一样,但ip确是不一样的,外界该要怎么访问这个pod呢?
# 答:使用SVC来实现
[root@master1 ~]# kubectl expose --help|grep -A 1 'Create a service for an nginx'
# Create a service for an nginx deployment, which serves on port 80 and connects to the containers on port 8000.
kubectl expose deployment nginx --port=80 --target-port=8000

# 查看一下deployment名称
[root@master1 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
httpd-01 3/3 3 3 13h

# 创建svc
[root@master1 ~]# kubectl expose deployment httpd-01 --port=88 --target-port=80
service/httpd-01 exposed

# 查看一下svc地址
[root@master1 ~]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
httpd-01 ClusterIP 10.99.191.162 <none> 88/TCP 30s run=httpd-01
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13h <none>

# 这里为了好验证负载均衡,在访问之前先修改容器网页
[root@master1 ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-01-6c9fbcfb65-9hlhv 1/1 Running 1 13h 10.244.1.3 node1 <none> <none>
httpd-01-6c9fbcfb65-hjhjn 1/1 Running 0 23m 10.244.2.3 node2 <none> <none>
httpd-01-6c9fbcfb65-x52hh 1/1 Running 0 23m 10.244.2.2 node2 <none> <none>

[root@master1 ~]# kubectl exec -it httpd-01-6c9fbcfb65-9hlhv bash
root@httpd-01-6c9fbcfb65-9hlhv:/usr/local/apache2# echo 'node1-10.244.1.3' > htdocs/index.html

[root@master1 ~]# kubectl exec -it httpd-01-6c9fbcfb65-hjhjn bash
root@httpd-01-6c9fbcfb65-hjhjn:/usr/local/apache2# echo 'node2-10.244.2.3' > htdocs/index.html

[root@master1 ~]# kubectl exec -it httpd-01-6c9fbcfb65-x52hh bash
root@httpd-01-6c9fbcfb65-x52hh:/usr/local/apache2# echo 'node2-10.244.2.2' > htdocs/index.html

# 访问SVC从而以负载均衡的方式访问到pod副本
[root@master1 ~]# curl 10.99.191.162:88
node2-10.244.2.3
[root@master1 ~]# curl 10.99.191.162:88
node2-10.244.2.2
[root@master1 ~]# curl 10.99.191.162:88
node1-10.244.1.3
[root@master1 ~]# curl 10.99.191.162:88
node2-10.244.2.3

# 原理:查看下ipvsadm规则
[root@master1 ~]# ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.1:443 rr
-> 192.168.2.1:6443 Masq 1 3 0
TCP 10.96.0.10:53 rr
-> 10.244.0.6:53 Masq 1 0 0
-> 10.244.0.7:53 Masq 1 0 0
TCP 10.96.0.10:9153 rr
-> 10.244.0.6:9153 Masq 1 0 0
-> 10.244.0.7:9153 Masq 1 0 0
# 这就是刚才创建的SVC,实际上他就是一个转发规则
TCP 10.99.191.162:88 rr
-> 10.244.1.3:80 Masq 1 0 1
-> 10.244.2.2:80 Masq 1 0 1
-> 10.244.2.3:80 Masq 1 0 2
UDP 10.96.0.10:53 rr
-> 10.244.0.6:53 Masq 1 0 0
-> 10.244.0.7:53 Masq 1 0 0


# 上面做的其实只能在内部访问,如果像对外开放的话,需将原本svc的type类型改为NodePort类型,因为默认的ClusterIP类型只是针对这个集群,封闭不对外暴露的。
[root@master1 ~]# kubectl edit svc httpd-01
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2020-09-13T07:50:56Z"
labels:
run: httpd-01
name: httpd-01
namespace: default
resourceVersion: "21567"
selfLink: /api/v1/namespaces/default/services/httpd-01
uid: 624c99c1-b27f-4990-aa5b-d381e0a37755
spec:
clusterIP: 10.99.191.162
ports:
- port: 88
protocol: TCP
targetPort: 80
selector:
run: httpd-01
sessionAffinity: None
type: NodePort
status:
loadBalancer: {}

# 再来查看这个svc的类型
[root@master1 ~]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
httpd-01 NodePort 10.99.191.162 <none> 88:32552/TCP 28m run=httpd-01
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14h <none>

# 会发现它基于上面的88端口对外打开了一个随机端口32552提供服务,而且是将所有k8s节点都打开了这个端口号对外服务
[root@master1 ~]# netstat -antpu | grep 32552
tcp6 0 0 :::32552 :::* LISTEN 1945/kube-proxy

[root@node1 ~]# netstat -antpu | grep 32552
tcp6 0 0 :::32552 :::* LISTEN 1568/kube-proxy

[root@node2 ~]# netstat -antpu | grep 32552
tcp6 0 0 :::32552 :::* LISTEN 1601/kube-proxy

# 外界访问

kk

kkk

kkkk

kkkkk

至此整体kubernetes集群架构搭建完成

三、Kubectl常用命令

kubectl官方详细命令

1. 查看集群状态

1
2
3
4
5
# 查看客户端及服务端程序版本信息
kubectl version --short=true

# 查看集群信息
kubectl cluster-info

2. 创建资源对象

1
2
3
4
5
6
7
8
# 创建pod资源
kubectl run name --image=(镜像名) --replicas=(副本数) --port=(容器要暴露的端口) --labels=(设定自定义标签)

# 陈述式对象配置管理方式
kubectl create -f **.yaml

# 声明式对象配置管理方式(也适用于更新等)
kubectl apply -f **.yaml

3、查看资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看命名空间
kubectl get namespace

# -o 输出格式, wide表示plain-text
kubectl get pods,services -o wide

# -l 标签选择器(多个的话是与逻辑),-n 指定命名空间,不指定默认default
kubectl get pod -l "key=value,key=value" -n kube-system

# -l 基于集合的标签选择器, -L查询结果显示标签
# 注意: 为了避免和shell解释器解析!, 必须要为此类表达式使用单引号
kubectl get pod -l "key1 in (val1,val2),!key2" -L key

# -w 监视资源变动信息
kubectl get pod -w

4、打印容器中日志信息

1
2
# -f 持续监控,-c如果pod中只有一个容器不用加
kubectl logs name -f -c container_name -n kube-system

5、在容器中执行命令

1
2
3
4
5
# 容器外部执行命令
kubectl exec name -c container_name -n kube-system -- 具体命令

# 容器内部执行命令
kubectl exec -it pod_name /bin/sh 进入容器的交互式shell

6、删除资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 删除指定资源对象
kubectl delete [pods/services/deployments/...] name

# 删除kube-system下指定标签的资源对象
kubectl delete [pods/services/deployments/...] -l key=value -n kube-system

# 删除kube-system下所有资源对象
kubectl delete [pods/services/deployments/...] --all -n kube-system

# 强制删除Terminating的资源对象
kubectl delete [pods/services/deployments/...] source_name --force --grace-period=0 -n kube-system

# 一般不用这种方式删除
kubectl delete -f xx.yaml
kubectl apply -f xx.yaml --prune -l <labels>

# 默认删除控制器会同时删除其管控的所有Pod对象,加上cascade=false就只删除rs
kubectl delete rs rs_name --cascade=false

7、更新资源对象

1
2
# --force 如果需要基于此前的配置文件进行替换,需要加上force
kubectl replace -f xx.yaml --force

8、将服务暴露出去(创建Service)

1
kubectl expose deployments/deployment_name --type="NodePort" --port=(要暴露的容器端口) --name=(Service对象名字)

9、扩容和缩容

1
2
3
4
kubectl scale deployment/deployment_name --replicas=N

# 只有当前副本数等于M时才会执行扩容或者缩容
kubectl scale deployment/deployment_name --replicas=N --current-replicas=M

10、查看API版本

1
kubectl api-versions

11、在本地主机上为API Server启动一个代理网关

1
2
3
4
5
# 之后就可以通过curl来对此套字节发起访问请求
kubectl proxy --port=8080

# jq可以对json进行过滤)
curl localhost:8080/api/v1/namespaces/ | jq .items[].metadata.name

12、当定义资源配置文件时,不知道怎么定义的时候,可以查看某类型资源的配置字段解释

1
2
# 二级对象可用类似于pods.spec这种方式查看
kubectl explain pods/deployments/...

13、查看某资源对象的配置文件

1
2
# --export表示省略由系统生成的信息, 后面加 > file.yaml就可以快速生成一个配置文件了
kubectl get source_type source_name -o yaml --export

14、标签管理相关命令

1
2
3
4
5
# 添加标签,如果是修改的话需要后面添加--overwrite
kubectl label pods/pod_name key=value

# 给工作节点添加标签,后续可以使用nodeSelector来指定pod被调度到指定的工作节点上运行
kubectl label nodes node_name key=value

15、注解管理相关命令

1
kubectl annotate pods pod_name key=value

16、patch修改Deployment控制器进行控制器升级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -p 以补丁形式更新补丁形式默认是json
kubectl patch deployment deployment-demo -p '{"spec": {"minReadySeconds": 5}}'

# 修改depolyment中的镜像文件
kubectl set image deployments deployment-demo myapp=ikubernetes/myapp:v2

# 打印滚动更新过程中的状态信息
kubectl rollout status deployment deployment-demo

# 监控deployment的更新过程
kubectl get deployments deployment-demo --watch

# 暂停更新
kubectl rollout pause deployments deployment-demo

# 继续更新
kubectl rollout resume deployments deployment-demo

# 查看历史版本(能查到具体的历史需要在apply的时候加上--record参数)
kubectl rollout history deployments deployment-demo

# 回滚到指定版本,不加--to-version则回滚到上一个
kubectl rollout undo deployments deployment-demo --to-revision=2

四、k8s的资源清单(重点)

1. 资源类型

k8s中所有的资源都会抽象为资源,资源实例化之后,叫做对象。

分为以下三种:

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

1.1 名称空间级别

  • 服务发现及负载均衡型资源( ServiceDiscovery LoadBalance): Service(SVC)、Ingress、……

  • 配置与存储型资源: Volume(存储卷)、CSI(容器存储接口,可以扩展各种各样的第三方存储卷)

  • 特殊类型的存储卷: ConfigMap(当配置中心来使用的资源类型)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)

  • 工作负载型资源(workload): Pod(pause)、Replicase、 Deployment、StatefulSet、DaemonSet、Job、Cron Job(Replication Controller在v1.11版本被废弃)

1.2 集群级资源级别

  • Namespace
  • Node
  • Role
  • Clusterrole
  • RoleBinding
  • ClusterRoleBinding

1.3 元数据型资源

  • HPA

  • PodTemplate

  • Limi tRange

2. 资源清单

在k8s中,一般使用yaml格式的文件来创建符合我们预期期望的pod,这样的yaml文件我们一般称为资源清单

2.1 yaml语法格式

是一个可读性高,用来表达数据序列的格式。YAML 的意思其实是:仍是一种标记语言,但为了强调这种语言以数据做为中心,而不是以标记语言为重点。

基本语法

  • 缩进时不允许使用Tab键,只允许使用空格
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • # 标识注释,从这个字符一直到行尾,都会被解释器忽略

YAML 支持的数据结构

  • 对象:键值对的集合,又称为映射(mapping)、哈希(hashes)、字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence)、列表(list)
  • 纯量(scalars):单个的、不可再分的值

对象类型:对象的一组键值对,使用冒号结构表示

1
2
name: Steve
age: 18

Yaml 也允许另一种写法,将所有键值对写成一个行内对象

1
hash: { name: Steve, age: 18 }

数组类型:一组连词线开头的行,构成一个数组

1
2
3
animal
- Cat
- Dog

数组也可以采用行内表示法

1
animal: [Cat, Dog]

复合结构:对象和数组可以结合使用,形成复合结构

1
2
3
4
5
6
7
8
9
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org

纯量:纯量是最基本的、不可再分的值。以下数据类型都属于纯量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1 字符串布尔值整数浮点数 Null
2 时间 日期

数值直接以字面量的形式表示
number: 12.30

布尔值用true和false表示
isSet: true

null ~ 表示
parent: ~

时间采用 ISO8601 格式
iso8601: 2001-12-14t21:59:43.10-05:00

日期采用复合 iso8601 格式的年、月、日表示
date: 1976-07-31

YAML允许使用两个感叹号,强制转换数据类型
e: !!str 123
f: !!str true

字符串

字符串默认不使用引号表示

1
str: 这是一行字符串

如果字符串之中包含空格或特殊字符,需要放在引号之中

1
str: '内容:字符串'

单引号和双引号都可以使用,双引号不会对特殊字符转义

1
2
s1: '内容\n字符串'
s2: "内容\n字符串"

单引号之中如果还有单引号,必须连续使用两个单引号转义

1
str: 'labor''s day'

字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格

1
2
3
str: 这是一段
多行
字符串

多行字符串可以使用|保留换行符,也可以使用>折叠换行

1
2
3
4
5
6
7
this: |
Foo
Bar

that: >
Foo
Bar

+ 表示保留文字块末尾的换行,- 表示删除字符串末尾的换行

1
2
3
4
5
6
7
8
s1: |
Foo

s2: |+
Foo

s3: |-
Foo

2.2 k8s剧本的常用字段

必须存在的属性

参数名 字段类型 说明
apiVersion String K8S API 的版本,目前基本是v1,可以用 kubectl api-version 命令查询
kind String 这里指的是 yaml 文件定义的资源类型和角色, 比如: Pod
metadata Object 元数据对象,固定值写 metadata
metadata.name String 元数据对象的名字,这里由我们编写,比如命名Pod的名字
metadata.namespace String 元数据对象的命名空间,由我们自身定义
Spec Object 详细定义对象,固定值写Spec
spec.containers[] list 这里是Spec对象的容器列表定义,是个列表
spec.containers[].name String 这里定义容器的名字
spec.containers[].image String 这里定义要用到的镜像名称

主要属性【这些属性比较重要,如果不指定的话系统会自动补充默认值】

参数名称 字段类型 说明
spec.containers[].name String 这里定义容器的名字
spec.containers[].image String 这里定义要用到的镜像名称
spec.containers[].imagePullPolicy String 定义镜像拉取策略,有Always、Never、IfNotPresent三个值可选(1)Always:意思是每次都尝试重新拉取镜像(2)Never:表示仅使用本地镜像(3)lfNotPresent:如果本地有镜像就使用本地镜像,没有就拉取在线镜像。上面三个值都没设置的话,默认是Always。
spec.containers[].command[] List 指定容器启动命令,因为是数组可以指定多个,不指定则使用镜像打包时使用的启动命令。
spec.containers[].args[] List 指定容器启动命令参数,因为是数组可以指定多个。
spec.containers[].workingDir String 指定容器的工作目录,进入容器时默认所在的目录
spec.containers[].volumeMounts[] List 指定容器内部的存储卷配置
spec.containers[].volumeMounts[].name String 指定可以被容器挂载的存储卷的名称
spec.containers[].volumeMounts[].mountPath String 指定可以被容器挂载的存储卷的路径
spec.containers[].volumeMounts[].readOnly String 设置存储卷路经的读写模式,true或者false,默认为读写模式
spec.containers[].ports[] List 指定容器需要用到的端口列表
spec.containers[].ports[].name String 指定端口名称
spec.containers[].ports[].containerPort String 指定容器需要监听的端口号
spec.containers[].ports[].hostPort String 指定容器所在主机需要监听的端口号,默认跟上面containerPort相同,注意设置了hostPort同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突)
spec.containers[].ports[].protocol String 指定端口协议,支持TCP和UDP,默认值为 TCP
spec.containers[].env[] List 指定容器运行前需设置的环境变量列表
spec.containers[].env[].name String 指定环境变量名称
spec.containers[].env[].value String 指定环境变量值
spec.containers[].resources Object 指定资源限制和资源请求的值(这里开始就是设置容器的资源上限)
spec.containers[].resources.limits Object 指定设置容器运行时资源的运行上限
spec.containers[].resources.limits.cpu String 指定CPU的限制,单位为core数,将用于docker run –cpu-shares参数这里前面文章 Pod资源限制有讲过)
spec.containers[].resources.limits.memory String 指定MEM内存的限制,单位为MlB、GiB
spec.containers[].resources.requests Object 指定容器启动和调度时的限制设置
spec.containers[].resources.requests.cpu String CPU请求,单位为core数,容器启动时初始化可用数量
spec.containers[].resources.requests.memory String 内存请求,单位为MIB、GiB,容器启动的初始化可用数量

额外的参数项

参数名称 字段类型 说明
spec.restartPolicy String 定义Pod的重启策略,可选值为Always、OnFailure,默认值为Always。1.Always:Pod一旦终止运行,则无论容器是如何终止的,kubelet服务都将重启它。2.OnFailure:只有Pod以非零退出码终止时,kubelet才会重启该容器。如果容器正常结束(退出码为0),则kubelet将不会重启它。3.Never:Pod终止后,kubelet将退出码报告给Master,不会重启该Pod。
spec.nodeSelector Object 定义Node的Label过滤标签,以key:value格式指定,选择node节点去运行
spec.imagePullSecrets Object 定义pull镜像时使用secret名称,以name:secretkey格式指定
spec.hostNetwork Boolean 定义是否使用主机网络模式,默认值为false。设置true表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本。

查看资源有那些资源清单属性,使用以下命令

1
kubectl explain pod

查看属性说明,使用以下命令

1
kubectl explain pod.apiVersion

资源清单格式

1
2
3
4
5
6
7
8
9
apiVersion: group/apiversion     # 如果没有给定group名称,那么默认为core,可以使用kubectlapi-versions命令获取当前k8s版本上所有的apiversion版本信息(每个版本可能不同)
kind: # 资源类别
metadata: # 资源元数据
name:
namespace:
lables:
annotations: # 主要目的是方便用户阅读查找
spec: # 期望的状态(disired state)
status: # 当前状态,本字段由Kubernetes自身维护,用户不能去定义

资源清单的常用命令

1.获取apiVersion版本信息

1
kubectl api-versions

2.获取资源的apiVersion的版本信息(以pod为例),该命令同时输出属性设置帮助文档

1
kubectl explain pod

字段配置格式

1
2
3
4
5
6
7
8
apiVersion <string>            # 表示字符串类型 
metadata <Object> # 表示需要嵌套多层字段
labels <map[string]string> # 表示由k:v组成的映射
finalizers <[]string> # 表示字串列表
ownerReferences <[]Object> # 表示对象列表
hostPID <boolean> # 布尔类型
priority <integer> # 整型
name <string> -required- # 如果类型后面接-required-,表示为必填字段

示例:通过定义清单文件创建Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 当前K8S API 的版本
apiVersion: v1
# 资源类型
kind: Pod
# 元数据
metadata:
# pod名称
name: k8s-apache-pod
# pod的名称空间
namespace: default
# pod的标签
labels:
app: httpd
# 对象的详细信息
spec:
# 容器信息
containers:
# 容器名称
- name: web01
# 这个容器使用的镜像
image: hub.lemon.com/library/httpd:v1

通过yaml文件创建pod

1
kubectl apply -f xxx.yaml

获取资源的资源配置文件

1
2
# 使用 -o 参数 加 yaml,可以将资源的配置以yaml的格式输出出来,也可以使用json,输出为json格式
kubectl get pod {podName} -o yaml

3、pod的生命周期

pod对象自从创建开始至终止退出的时间范围称为生命周期,在这段时间中,pod会处于多种不同的状态,并执行一些操作;其中,创建主容器为必须的操作,其他可选的操作还包括运行初始化容器(init container)、容器启动后钩子(start hook)、容器的存活性探测(liveness probe)、就绪性探测(readiness probe)以及容器终止前狗子(pre stop hook)等,这些操作是否执行则取决于pod的定义

image-20200919181303358

init C & start-stop & readiness & liveness 整体流程:

1、kubectl 调用 apiserver –> etcd –> kubelet –> CRI 进行容器初始化

2、首先会先启动一个pause容器(任何pod启动时都会先pause容器)

3、init C 容器不会伴随整个pod的生命周期;如果是正常退出(0),进入main C,否则(非0),一直重启 失败。

4、进入main C后有两个参数,start:在刚启动时可以允许他执行一个命令,stop:退出时也可以允许他执行一个命令。

5、readiness模板:可以在main C运行的多少秒之后进行就绪检测;检测通过后,pod状态就为running,否则为failed。

6、liveness模板:伴随着整个main C的生命周期,当main C里面的进程 与 liveness检测结果不一致时,就可以执行对应的命令。

3.1 Init C

Pod能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的Init容器

Init容器与普通的容器非常像,除了如下两点:

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

如果Pod的Init容器失败,Kubernetes会不断地重启该Pod,直到Init容器成功为止。然而,如果Pod对应的restartPolicy为Never,它不会重新启动, 因为Init容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:

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

Init C 特殊说明:

  • 在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容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误

Init 容器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: Pod
metadata:
name: lemon-pod
labels:
app: lemon01
spec:
# main容器字段
containers:
- name: main-container
image: busybox:latest
# 容器启动后执行的命令
command: ['sh', '-c', 'echo The main-container is running! && sleep 3600']
# init容器字段
initContainers:
# 第一个init容器
- name: init-container01
image: busybox:latest
command: ['sh', '-c', 'until test -e /live01; do echo waiting for live01; sleep 10; done;']
# 第二个init容器
- name: init-container02
image: busybox:latest
command: ['sh', '-c', 'until test -e /live02; do echo waiting for live02; sleep 10; done;']
1
2
$ kubectl exec -it lemon-pod -c init-container01 -- touch /live01
$ kubectl exec -it lemon-pod -c init-container02 -- touch /live02

3.2 容器探针

探针是由kubelet对容器执行的定期诊断。要执行诊断,kubelet调用由容器实现的Handler。

探测方式:

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

有三种类型的处理程序:

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

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

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

Init 容器init 模板检测探针 - 就绪检测

readinessProbe-httpget

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: default
labels:
app: lemon02
spec:
containers:
- name: readiness-httpget-container
image: httpd:latest
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index01.html
initialDelaySeconds: 1
periodSeconds: 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness-httpget-pod 0/1 Running 0 11m

$ kubectl log -f readiness-httpget-pod
10.244.1.1 - - [25/Feb/2021:08:19:11 +0000] "GET /index01.html HTTP/1.1" 404 196
10.244.1.1 - - [25/Feb/2021:08:19:14 +0000] "GET /index01.html HTTP/1.1" 404 196

$ kubectl exec -it readiness-httpget-pod -- /bin/sh
# echo 'lemon' > /usr/local/apache2/htdocs/index01.html

$ kubectl log -f readiness-httpget-pod
10.244.1.1 - - [25/Feb/2021:08:28:53 +0000] "GET /index01.html HTTP/1.1" 200 6

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness-httpget-pod 1/1 Running 0 22m

liveness 检测探针 - 存活检测

livenessProbe-exec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
labels:
app: lemon03
spec:
containers:
- name: liveness-exec-container
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "touch /tmp/live ; sleep 60; rm -rf /tmp/live; sleep 3600"]
livenessProbe:
exec:
command: ["test", "-e", "/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3
1
2
3
4
5
6
7
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 0 9s

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 1 100s

livenessProbe-httpget

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: liveness-httpget-pod
namespace: default
spec:
containers:
- name: liveness-httpget-container
image: httpd:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
port: http
path: /index.html
initialDelaySeconds: 1
periodSeconds: 3
timeoutSeconds: 10
1
2
3
4
5
6
7
8
9
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-httpget-pod 1/1 Running 0 102s

$ kubectl exec -it liveness-httpget-pod -- rm /usr/local/apache2/htdocs/index.html

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-httpget-pod 1/1 Running 1 2m53s

livenessProbe-tcp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: probe-tcp
spec:
containers:
- name: nginx
image: nginx:latest
livenessProbe:
initialDelaySeconds: 5
timeoutSeconds: 1
tcpSocket:
port: 8080

# 一段时间时候就会自动重启pod,原因nginx容器没开8080端口,tomcat就不会重启

3.3 Pod hook

Podhook(钩子)是由Kubernetes管理的kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。可以同时为Pod中的所有容器都配置hook。

Hook的类型包括两种:

  • exec:执行一段命令
  • HTTP:发送HTTP请求

start、stop 动作

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:latest
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /var/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo Hello from the poststop handler > /var/message"]

3.4 Init C & 探针 整合使用

init-readiness-liveness-hook.yaml

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
apiVersion: v1
kind: Pod
metadata:
name: lemon-pod-irlp
labels:
app: lemon01
spec:
initContainers:
- name: init-container01
image: busybox:latest
command: ['sh', '-c', 'until test -e /live01; do echo waiting for live01; sleep 10; done;']
- name: init-container02
image: busybox:latest
command: ['sh', '-c', 'until test -e /live02; do echo waiting for live02; sleep 10; done;']
containers:
- name: main-container
image: busybox:latest
command: ['sh', '-c', 'echo The main-container is running! && sleep 3600']
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index01.html
initialDelaySeconds: 1
periodSeconds: 3
livenessProbe:
exec:
command: ["test", "-e", "/tmp"]
initialDelaySeconds: 1
periodSeconds: 3
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /var/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo Hello from the poststop handler > /var/message"]
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
# init_c
$ kubectl exec -it lemon-pod-irlp -c init-container01 -- touch /live01
$ kubectl exec -it lemon-pod-irlp -c init-container02 -- touch /live02
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
lemon-pod-irlp 0/1 Running 0 51m

# Readiness
$ kubectl describe pod lemon-pod-irlp
Readiness probe failed: Get http://10.244.2.12:80/index01.html
$ kubectl exec -it lemon-pod-irlp -c main-container -- /bin/sh
/ # echo 'lemon' > /var/www/index01.html && httpd -p 80 -h /var/www/ && netstat -anptu | grep httpd
tcp 0 0 :::80 :::* LISTEN 287/httpd
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
lemon-pod-irlp 1/1 Running 0 4m42s

# Liveness
$ kubectl exec -it lemon-pod-irlp -c main-container -- rm -rf /tmp/
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
lemon-pod-irlp 1/1 Running 0 5m35s
lemon-pod-irlp 0/1 Running 1 6m1s
$ kubectl describe pod lemon-pod-irlp
Liveness probe failed:

# Pod hook
$ kubectl exec -it lemon-pod-irlp -- cat /var/message
Hello from the postStart handler

3.5 Pod的相位

Pod的status字段是一个PodStatus对象,PodStatus中有一个phase字段。

无论是手动创建还是通过控制器创建pod,pod对象总是应该处于其生命进程中以下几个相位之一:

挂起(Pending):Pod已被Kubernetes系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度Pod的时间和通过网络下载镜像的时间,这可能需要花点时间。

运行中(Running):该Pod已经绑定到了一个节点上,Pod中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态。

成功(Succeeded):Pod中的所有容器都被成功终止,并且不会再重启。

失败(Failed):Pod中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。

未知(Unknown):因为某些原因无法取得Pod的状态,通常是因为与Pod所在主机通信失败。

4、controllers 控制器

kubernetes不会直接创建pod,而是通过controller来管理pod的,所以controller负责维护集群的状态,比如故障检测、自动扩展、滚动更新。

Kubernetes中会有很多的controller(控制器),这些就相当于一个状态机(快照),用来控制Pod的具体状态和行为。

1. ReplicationController

ReplicationController(RC)用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来替代;而如果异常多出来的容器也会自动回收;

2. ReplicaSet

在新版本的 Kubernetes 中建议使用 ReplicaSet来取代 ReplicationController 。ReplicaSet 跟ReplicationController 没有本质的不同,只是名字不一样,并且 ReplicaSet 支持集合式的 selector;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
tier: web
template:
metadata:
labels:
tier: web
spec:
containers:
- name: nginx
image: nginx:latest
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 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
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
frontend-d8rs5 1/1 Running 0 29s tier=web
frontend-gczvb 1/1 Running 0 29s tier=web
frontend-twj7z 1/1 Running 0 29s tier=web

$ kubectl label pod frontend-d8rs5 tier=lemon --overwrite=true
pod/frontend-d8rs5 labeled
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
frontend-d8rs5 1/1 Running 0 3m3s tier=lemon
frontend-gczvb 1/1 Running 0 3m3s tier=web
frontend-mrm8n 1/1 Running 0 10s tier=web
frontend-twj7z 1/1 Running 0 3m3s tier=web

$ kubectl delete pod --all
pod "frontend-d8rs5" deleted
pod "frontend-gczvb" deleted
pod "frontend-mrm8n" deleted
pod "frontend-twj7z" deleted
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
frontend-26xlj 1/1 Running 0 11s tier=web
frontend-m4vhl 1/1 Running 0 11s tier=web
frontend-zs9tk 1/1 Running 0 11s tier=web

# 删除 RS
$ kubectl delete rs frontend -n default

3. Deployment

Deployments(无状态,守护进程类,只关注群体不关注个体)

  • 一个 Deployment 控制器为 Pods和 ReplicaSets 提供声明式的更新能力。
  • 虽然 ReplicaSet可以独立使用,但一般还是建议使用 Deployment来自动管理ReplicaSe,这样就无需担心跟其他机制的不兼容问题(比如 ReplicaSe不支持rolling-update 但 Deployment支持)。

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,用来替代以前的ReplicationController 来方便的管理应用。典型的应用场景包括;

  • 定义 Deployment 来创建 Pod 和 ReplicaSet
  • 应用扩容和缩容
  • 滚动升级和回滚
  • 暂停和继续 Deployment

3.1 创建一个 deployment 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 62s

$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-687dc75df7 3 3 3 65s

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-687dc75df7-88tkk 1/1 Running 0 68s
nginx-deployment-687dc75df7-b6whp 1/1 Running 0 68s
nginx-deployment-687dc75df7-ljh46 1/1 Running 0 68s

3.2 Deployment 扩容缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ kubectl scale deployment nginx-deployment --replicas=5
deployment.extensions/nginx-deployment scaled
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-687dc75df7-88tkk 1/1 Running 0 7m22s
nginx-deployment-687dc75df7-b6whp 1/1 Running 0 7m22s
nginx-deployment-687dc75df7-ljh46 1/1 Running 0 7m22s
nginx-deployment-687dc75df7-rnjzt 1/1 Running 0 8s
nginx-deployment-687dc75df7-wfx74 1/1 Running 0 8s

$ kubectl scale deployment nginx-deployment --replicas=2
deployment.extensions/nginx-deployment scaled
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-deployment-687dc75df7-88tkk 1/1 Running 0 8m18s
nginx-deployment-687dc75df7-ljh46 1/1 Running 0 8m18s

如果集群支持 horizontal pod autoscaling 的话,还可以为Deployment设置自动扩展

1
kubectl autoscale deployment nginx-deployment --min=10--max=15--cpu-percent=80

3.3 滚动升级和回滚

Deployment 更新策略

Deployment 可以保证在升级时只有一定数量的 Pod 是 down 的。默认的,它会确保至少有比期望的Pod数量少一个是up状态(最多一个不可用)。

Deployment 同时也可以确保只创建出超过期望数量的一定数量的 Pod。默认的,它会确保最多比期望的Pod数量多一个的 Pod 是 up 的(最多1个 surge )。

未来的 Kuberentes 版本中,将从1-1变成25%-25% 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 滚动升级
$ kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
$ kubectl describe deploy nginx-deployment | grep -i 'image'
Image: nginx:1.9.1

# 回滚
$ kubectl rollout undo deployment/nginx-deployment
$ kubectl describe deploy nginx-deployment | grep -i 'image'
Image: nginx:1.7.9

# 也可以使用edit命令来编辑 Deployment
$ kubectl edit deployment nginx-deployment
40 - image: nginx:latest
deployment.extensions/nginx-deployment edited
$ kubectl describe deploy nginx-deployment | grep -i 'image'
Image: nginx:latest

# 更新镜像实际上就是重新创建了一个rs
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-5458686b99 2 2 2 23s
nginx-deployment-687dc75df7 0 0 0 18m
nginx-deployment-6df444bb79 0 0 0 5m55s

Rollover(多个rollout并行)

假如您创建了一个有5个niginx:1.7.9 replica的 Deployment,但是当还只有3个nginx:1.7.9的 replica 创建出来的时候您就开始更新含有5个nginx:1.9.1 replica 的 Deployment。在这种情况下,Deployment 会立即杀掉已创建的3个nginx:1.7.9的 Pod,并开始创建nginx:1.9.1的 Pod。它不会等到所有的5个nginx:1.7.9的Pod 都创建完成后才开始改变航道。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ kubectl rollout history deployment nginx-deployment
deployment.extensions/nginx-deployment
REVISION CHANGE-CAUSE
2 <none>
3 <none>
4 <none>

# 可以使用 --revision参数指定某个历史版本
$ kubectl rollout undo deployment nginx-deployment --to-revision=2
deployment.extensions/nginx-deployment rolled back
$ kubectl describe deploy nginx-deployment | grep -i 'image'
Image: nginx:1.9.1


$ ps: kubectl rollout pause deployment nginx-deployment ## 暂停 deployment 的更新

# 删除DeployMent
$ kubectl delete deployment nginx-deployment -n default

清理 Policy

可以通过设置.spec.revisonHistoryLimit项来指定 deployment 最多保留多少 revision 历史记录。默认的会保留所有的 revision;如果将该项设置为0,Deployment 就不允许回退了

4. DaemonSet

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

使用 DaemonSet 的一些典型用法:

  • 运行集群存储 daemon,例如在每个 Node 上运行glusterd、ceph
  • 在每个 Node 上运行日志收集 daemon,例如fluentd、logstash
  • 在每个 Node 上运行监控 daemon,例如Prometheus Node Exporter、collectd、Datadog 代理、New Relic 代理,或 Ganglia gmond
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: deamonset-example
labels:
app: daemonset
spec:
selector:
matchLabels:
name: deamonset-example
template:
metadata:
labels:
name: deamonset-example
spec:
containers:
- name: daemonset-example
image: nginx:latest
1
2
3
4
5
6
7
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
deamonset-example-tqmfx 1/1 Running 0 94s
deamonset-example-wqvsm 1/1 Running 0 94s

# 删除 DaemonSet
$ kubectl delete daemonset deamonset-example -n default

5. Jobs :一次性任务

一次性任务,运行完成后pod销毁,不再重新启动新容器。还可以任务定时运行。

Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束

特殊说明:

  • spec.template 格式同 Pod

  • RestartPolicy 仅支持 Never 或 OnFailure

  • 单个Pod时,默认 Pod 成功运行后 Job 即结束

  • spec.completions 标志 Job 结束需要成功运行的 Pod 个数,默认为1

  • spec.parallelism 标志并行运行的 Pod 的个数,默认为1

  • spec.activeDeadlineSeconds 标志失败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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl get job
NAME COMPLETIONS DURATION AGE
pi 1/1 3m26s 8m54s

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
pi-bfmq8 0/1 Completed 0 9m1s

$ kubectl log -f pi-bfmq8
log is DEPRECATED and will be removed in a future version. Use logs instead.
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275905

# 删除 Job
$ kubectl delete job pi -n default

6. CronJob :定时任务

cronjob 其实就是在job的基础上加上了时间调度,可以:在给定的时间点运行一个任务,也可以周期性的在给定时间点运行。(与linux中的crontab类似)

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

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

使用前提条件:当前使用的Kubernetes集群,版本 >= 1.8(对 CronJob)。对于先前版本的集群,版本 <1.8,启动 API Server时,通过传递选项–runtime-config=batch/v2alpha1=true可以开启 batch/v2alpha1API。

典型的用法如下所示:

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

特殊说明:

  • spec.template 格式同 Pod

  • RestartPolicy 仅支持 Never 或 OnFailure

  • 单个 Pod 时,默认 Pod 成功运行后 Job 即结束

  • spec.completions 标志 Job 结束需要成功运行的 Pod 个数,默认为1

  • spec.parallelism 标志并行运行的 Pod 的个数,默认为1

  • spec.activeDeadlineSeconds 标志失败 Pod 的重试最大时间,超过这个时间不会继续重试

  • spec.schedule:调度,必需字段,指定任务运行周期,格式同 Cron

  • spec.jobTemplate:Job 模板,必需字段,指定需要运行的任务,格式同 Job

  • spec.startingDeadlineSeconds:启动 Job 的期限(秒级别),该字段是可选的。如果因为任何原因而错过了被调度的时间,那么错过执行时间的 Job 将被认为是失败的。如果没有指定,则没有期限

  • spec.concurrencyPolicy:并发策略,该字段也是可选的。它指定了如何处理被 Cron Job 创建的 Job 的并发执行。只允许指定下面策略中的一种:

    • Allow(默认):允许并发运行 Job

    • Forbid:禁止并发运行,如果前一个还没有完成,则直接跳过下一个

    • Replace:取消当前正在运行的 Job,用一个新的来替换

      注意, 当前策略只能应用于同一个Cron Job创建的Job。如果存在多个Cron Job, 它们创建的 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
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ kubectl get cronjob
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 1 10s 22s

$ kubectl get job
NAME COMPLETIONS DURATION AGE
hello-1614322500 1/1 3s 62s
hello-1614322560 0/1 2s 2s

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
hello-1614322500-p7wmh 0/1 Completed 0 6s
hello-1614322560-vsz58 0/1 Completed 0 27s

$ kubectl logs -f hello-1614322500-p7wmh
Sat Mar 20 13:03:17 UTC 2021
Hello from the Kubernetes cluster

# 删除 cronjob
$ kubectl delete cronjob hello -n default

7. StateFulSet

StatefulSets(管理有状态应用) 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:

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

8. Horizontal Pod Autoscaler

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

5、Service

Kubernetes Service定义了这样一种抽象:一个Pod的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。这一组Pod能够被Service访问到,通常是通过Label Selector 。

image-20210301102544478

Service能够提供负载均衡的能力,但是在使用上有个限制:Service 只提供 4 层负载均衡能力,而没有 7 层功能, 但有时可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的。

5.1 Service 的类型

Service 在 K8s 中有以下四种类型:

  • ClusterIp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP
  • NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过: NodePort 来访问该服务
  • LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到: NodePort
  • ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持

image-20210301102927561

5.2 VIP 和 Service 代理

在 Kubernetes 集群中,每个 Node 运行一个kube-proxy进程。kube-proxy负责为Service实现了一种VIP(虚拟 IP)的形式,而不是ExternalName的形式。在 Kubernetes v1.0 版本,代理完全在 userspace。在Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。从 Kubernetes v1.2 起,默认就是iptables 代理。在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理。

在 Kubernetes 1.14 版本开始默认使用ipvs 代理。

在 Kubernetes v1.0 版本,Service是 “4层”(TCP/UDP over IP)概念。在 Kubernetes v1.1 版本,新增了Ingress API(beta 版),用来表示 “7层”(HTTP)服务。

代理模式的分类如下:

1、userspace 代理模式

image-20210301103543203

2、iptables 代理模式

image-20210301103602493

3、ipvs 代理模式

这种模式,kube-proxy 会监视 Kubernetes Service对象和Endpoints,调用netlink接口以相应地创建ipvs 规则并定期与 Kubernetes Service对象和Endpoints对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod。

与 iptables 类似,ipvs 于 netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs 为负载均衡算法提供了更多选项,例如:

  • rr:轮询调度
  • lc:最小连接数
  • dh:目标哈希
  • sh:源哈希
  • sed:最短期望延迟
  • nq:不排队调度

image-20210301103456348

5.3 ClusterIP

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

image-20210301111213452

为了实现图上的功能,主要需要以下几个组件的协同工作:

  • apiserver 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中
  • kube-proxy kubernetes的每个节点中都有一个叫做kube-porxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的 IPVS 规则中
  • IPVS 使用NAT等技术将virtualIP的流量转至endpoint中

创建 myapp-deploy-svc.yaml 文件

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
template:
metadata:
labels:
app: myapp
release: stabel
env: test
spec:
containers:
- name: myapp
image: nginx:latest
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
type: ClusterIP
selector:
app: myapp
release: stabel
ports:
- name: http
port: 80
targetPort: 80

查看 IPVSADM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ kubectl get svc -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 172d <none>
myapp ClusterIP 10.104.132.57 <none> 80/TCP 3m10s app=myapp,release=stabel

$ ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.0.1:443 rr
-> 192.168.2.1:6443 Masq 1 3 0
TCP 10.96.0.10:53 rr
-> 10.244.0.14:53 Masq 1 0 0
-> 10.244.0.15:53 Masq 1 0 0
TCP 10.96.0.10:9153 rr
-> 10.244.0.14:9153 Masq 1 0 0
-> 10.244.0.15:9153 Masq 1 0 0
TCP 10.104.132.57:80 rr
-> 10.244.1.40:80 Masq 1 0 3
-> 10.244.1.41:80 Masq 1 0 3
-> 10.244.2.40:80 Masq 1 0 3
UDP 10.96.0.10:53 rr
-> 10.244.0.14:53 Masq 1 0 0
-> 10.244.0.15:53 Masq 1 0 0

5.4 Headless Service

有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 ClusterIP(spec.clusterIP) 的值为 “None” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: myapp-headless
namespace: default
spec:
selector:
app: myapp
clusterIP: "None"
ports:
- port: 80
targetPort: 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
$ kubectl get svc -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 172d <none>
myapp ClusterIP 10.104.132.57 <none> 80/TCP 5m30s app=myapp,release=stabel
myapp-headless ClusterIP None <none> 80/TCP 19s app=myapp


$ dig -t A myapp-headless.default.svc.cluster.local. @10.96.0.10
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-16.P2.el7_8.6 <<>> -t A myapp-headless.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5421
;; flags: qr aa rd; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp-headless.default.svc.cluster.local. IN A

;; ANSWER SECTION:
myapp-headless.default.svc.cluster.local. 30 IN A 10.244.1.41
myapp-headless.default.svc.cluster.local. 30 IN A 10.244.1.40
myapp-headless.default.svc.cluster.local. 30 IN A 10.244.2.40

;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: 四 3月 04 10:36:53 CST 2021
;; MSG SIZE rcvd: 237

5.5 NodePort

nodePort 的原理在于在 node 上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步到给对应的 pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: myapps
namespace: default
spec:
type: NodePort
selector:
app: web
release: stabel
ports:
- name: http
port: 80
targetPort: 80
1
2
3
4
5
6
7
8
9
$ kubectl get svc -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 172d <none>
myapp ClusterIP 10.104.132.57 <none> 80/TCP 11m app=myapp,release=stabel
myapp-headless ClusterIP None <none> 80/TCP 6m42s app=myapp
myapps NodePort 10.100.41.94 <none> 80:32195/TCP 42s app=web,release=stabel

# 查询流程,并且在每台节点上都会打开一个32195端口
ipvsadm -Ln

5.6 LoadBalancer

loadBalancer 和 nodePort 其实是同一种方式。区别在于 loadBalancer 比 nodePort 多了一步,就是可以调用cloud provider 去创建 LB 来向节点导流。

image-20210301113038598

5.7 ExternalName

这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容( 例如:hub.atguigu.com )。

ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Service
metadata:
name: my-service-1
namespace: default
spec:
type: ExternalName
externalName: hub.armin.com
1
2
3
4
5
6
7
8
9
$ kubectl get svc -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 172d <none>
my-service-1 ExternalName <none> hub.armin.com <none> 29s <none>
myapp ClusterIP 10.104.132.57 <none> 80/TCP 21m app=myapp,release=stabel
myapp-headless ClusterIP None <none> 80/TCP 15m app=myapp
myapps NodePort 10.100.41.94 <none> 80:32195/TCP 9m51s app=web,release=stabel

$ dig -t A hub.armin.com.svc.cluster.local. @10.96.0.10

当查询主机 my-service.defalut.svc.cluster.local ( SVC_NAME.NAMESPACE.svc.cluster.local )时,集群的DNS 服务将返回一个值 my.database.example.com 的 CNAME 记录。访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。

6、Ingress

必须具有 Ingress 控制器 才能满足 Ingress 的要求。 仅创建 Ingress 资源本身没有任何效果。

需要部署 Ingress 控制器,例如 ingress-nginx。 你可以从许多 Ingress 控制器 中进行选择。

理想情况下,所有 Ingress 控制器都应符合参考规范。但实际上,不同的 Ingress 控制器操作略有不同。

mandatory.yaml

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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
# wait up to five minutes for the drain of connections
terminationGracePeriodSeconds: 300
serviceAccountName: nginx-ingress-serviceaccount
nodeSelector:
kubernetes.io/os: linux
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 101
runAsUser: 101
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
---
apiVersion: v1
kind: LimitRange
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
limits:
- min:
memory: 90Mi
cpu: 100m
type: Container

service-nodeport.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---

创建 ingress-nginx

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@k8s-master-192 ingress-nginx]# kubectl apply -f ./
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 configured
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 configured
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
service/ingress-nginx created

#查看ingress-nginx控制器
[root@k8s-master-192 ingress-nginx]# kubectl get pod -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-7fcf8df75d-7jj4z 1/1 Running 0 85s
nginx-ingress-controller-7fcf8df75d-l9bwc 1/1 Running 0 85s

#查看service
[root@k8s-master-192 ingress-nginx]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.100.56.91 <none> 80:31352/TCP,443:30499/TCP 80s

6.2 准备service 和 pod

为了方便后续的实验,创建下图所示的模型

9J8x1r

创建 tomcat-nginx.yaml

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: ingress-nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx-pod
template:
metadata:
labels:
app: nginx-pod
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deployment
namespace: ingress-nginx
spec:
replicas: 3
selector:
matchLabels:
app: tomcat-pod
template:
metadata:
labels:
app: tomcat-pod
spec:
containers:
- name: tomcat
image: tomcat:8.5-jre10-slim
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: ingress-nginx
spec:
selector:
app: nginx-pod
clusterIP: None
type: ClusterIP
ports:
- port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
namespace: ingress-nginx
spec:
selector:
app: tomcat-pod
clusterIP: None
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@k8s-master-192 ingress-nginx]# kubectl apply -f tomcat-nginx.yaml 
deployment.apps/nginx-deployment created
deployment.apps/tomcat-deployment created
service/nginx-service created
service/tomcat-service created

# 查看deploy
[root@k8s-master-192 ingress-nginx]# kubectl get deploy -n ingress-nginx
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 83s
nginx-ingress-controller 2/2 2 2 7m16s
tomcat-deployment 3/3 3 3 83s

# 查看svc
[root@k8s-master-192 ingress-nginx]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.100.56.91 <none> 80:31352/TCP,443:30499/TCP 6m42s
nginx-service ClusterIP None <none> 80/TCP 48s
tomcat-service ClusterIP None <none> 8080/TCP 48s

6.3 Http代理

创建ingress-http.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-http
namespace: dev
spec:
rules:
- host: nginx.test.com
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.test.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
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
26
27
28
29
30
31
[root@k8s-master-192 ingress-nginx]# kubectl apply -f ingress-http.yaml
ingress.extensions/ingress-http created

# 查看ingress规则
[root@k8s-master-192 ingress-nginx]# kubectl get ingress -n ingress-nginx
NAME HOSTS ADDRESS PORTS AGE
ingress-http nginx.test.com,tomcat.test.com 10.100.56.91 80 45s

# 查看详细信息
[root@k8s-master-192 ingress-nginx]# kubectl describe ing ingress-http -n ingress-nginx
Name: ingress-http
Namespace: ingress-nginx
Address: 10.100.56.91
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
nginx.test.com
/ nginx-service:80 (10.244.3.72:80,10.244.3.74:80,10.244.4.63:80) # 这些ip就是对应pod的
tomcat.test.com
/ tomcat-service:8080 (10.244.3.73:8080,10.244.4.64:8080,10.244.4.65:8080)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"ingress-http","namespace":"ingress-nginx"},"spec":{"rules":[{"host":"nginx.test.com","http":{"paths":[{"backend":{"serviceName":"nginx-service","servicePort":80},"path":"/"}]}},{"host":"tomcat.test.com","http":{"paths":[{"backend":{"serviceName":"tomcat-service","servicePort":8080},"path":"/"}]}}]}}

Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 109s nginx-ingress-controller Ingress ingress-nginx/ingress-http
Normal CREATE 109s nginx-ingress-controller Ingress ingress-nginx/ingress-http
Normal UPDATE 99s nginx-ingress-controller Ingress ingress-nginx/ingress-http
Normal UPDATE 99s nginx-ingress-controller Ingress ingress-nginx/ingress-http

修改本机的hosts文件,添加如下内容

1
2
master虚拟机的IP地址    nginx.test.com
masete虚拟机的IP地址 tomcat.test.com

查看ingress为service提供的端口号

1
2
[root@k8s-master-192 ingress-nginx]# kubectl get svc -n ingress-nginx|grep ingress-nginx
ingress-nginx NodePort 10.100.56.91 <none> 80:31352/TCP,443:30499/TCP 16m

在浏览器中测试访问, 是能够访问通

image-20210830105342190

image-20210830105435498

6.4 Https代理

创建证书

1
2
3
4
5
6
[root@k8s-master-192 ingress-nginx]# openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=test.com"
Generating a 2048 bit RSA private key
...........................................................................+++
....................................+++
writing new private key to 'tls.key'
-----

创建密钥

1
2
[root@k8s-master-192 ingress-nginx]# kubectl create secret tls tls-secret --key tls.key --cert tls.crt
secret/tls-secret created

创建 ingress-https.yaml

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
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-https
namespace: ingress-nginx
spec:
tls:
- hosts:
- nginx.test.com
- tomcat.test.com
secretName: tls-secret #指定密钥
rules:
- host: nginx.test.com
http:
paths:
- path: /
backend:
serviceName: nginx-service
servicePort: 80
- host: tomcat.test.com
http:
paths:
- path: /
backend:
serviceName: tomcat-service
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-192 ingress-nginx]# kubectl create -f ingress-https.yaml
ingress.extensions/ingress-https created

[root@k8s-master-192 ingress-nginx]# kubectl describe ing ingress-https -n ingress-nginx
Name: ingress-https
Namespace: ingress-nginx
Address: 10.100.56.91
Default backend: default-http-backend:80 (<none>)
TLS:
tls-secret terminates nginx.test.com,tomcat.test.com
Rules:
Host Path Backends
---- ---- --------
nginx.test.com
/ nginx-service:80 (10.244.3.72:80,10.244.3.74:80,10.244.4.63:80)
tomcat.test.com
/ tomcat-service:8080 (10.244.3.73:8080,10.244.4.64:8080,10.244.4.65:8080)
Annotations:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 40s nginx-ingress-controller Ingress ingress-nginx/ingress-https
Normal CREATE 40s nginx-ingress-controller Ingress ingress-nginx/ingress-https
Normal UPDATE 34s nginx-ingress-controller Ingress ingress-nginx/ingress-https
Normal UPDATE 34s nginx-ingress-controller Ingress ingress-nginx/ingress-https

获取端口,左边的是http使用的端口,右边是https使用的端口,因此要使用的端口是30499

1
2
[root@k8s-master-192 ingress-nginx]# kubectl get svc -n ingress-nginx|grep ingress-nginx
ingress-nginx NodePort 10.100.56.91 <none> 80:31352/TCP,443:30499/TCP 32m

使用浏览器访问

image-20210830111034769

image-20210830111006798

7、K8S - 存储

  • ConfigMap
  • Secret
  • volume
  • Persistent Volume(PV)

7.1 ConfigMap

ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象。

7.1.1 ConfigMap 的创建

  • 目录创建
  • 文件创建
  • 字面值创建

使用目录创建

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
$ ll /etc/kubernetes/configmap-dir/
game.properties
ui.properties


$ cat /etc/kubernetes/configmap-dir/game.properties
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30


$ cat /etc/kubernetes/configmap-dir/ui.properties
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice


# --from-file 指定在目录下的所有文件都会被用在 ConfigMap 里面创建一个键值对,键的名字就是文件名,值就是文件的内容。
$ kubectl create configmap game-config-1 --from-file=/etc/kubernetes/configmap-dir


$ kubectl get configmap -n default -owide
NAME DATA AGE
game-config-1 2 <invalid>


$ kubectl get configmap -n default -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
game.properties: |
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
ui.properties: |
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
kind: ConfigMap
metadata:
creationTimestamp: "2021-03-08T11:54:57Z"
name: game-config-1
namespace: default
resourceVersion: "15839"
selfLink: /api/v1/namespaces/default/configmaps/game-config-1
uid: cb84c4f5-e509-4ecb-8513-68184b8b57af
kind: List
metadata:
resourceVersion: ""
selfLink: ""


$ kubectl describe configmap game-config-1 -n default
Name: game-config-1
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
ui.properties:
----
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice

game.properties:
----
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30

Events: <none>

使用文件创建

只要指定为一个文件就可以从单个文件中创建 ConfigMap

1
2
3
$ kubectl create configmap game-config-2 --from-file=/etc/kubernetes/configmap-dir/game.properties
$ kubectl get configmaps game-config-2 -o yaml
# --from-file这个参数可以使用多次,你可以使用两次分别指定上个实例中的那两个配置文件,效果就跟指定整个目录是一样的。

使用字面值创建

使用文字值创建,利用–from-literal参数传递配置信息,该参数可以使用多次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ kubectl create configmap game-config-3 --from-literal=k1.how=v1 --from-literal=k2.how=v2
$ kubectl get configmaps game-config-3 -o yaml
apiVersion: v1
data:
k1.how: v1
k2.how: v2
kind: ConfigMap
metadata:
creationTimestamp: "2021-03-08T12:01:19Z"
name: game-config-3
namespace: default
resourceVersion: "16388"
selfLink: /api/v1/namespaces/default/configmaps/game-config-3
uid: b9e048f3-a17d-4517-bfe5-311c51377aaa

7.1.2 Pod 中使用 ConfigMap

使用 ConfigMap 来替代环境变量

  • env: 指定导入k/v
  • envFrom: 全部导入
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
# cat cm-test-01.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
special.sam: charm
special.lep: value
---
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
log_level: DEBUG
---
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod-01
spec:
containers:
- name: test-container
image: arminto/my_nginx:v1
command: [ "/bin/sh", "-c", "env" ]
env:
- name: SPECIAL_HOW_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
- name: SPECIAL_SAM_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.sam
envFrom:
- configMapRef:
name: env-config
restartPolicy: Never
1
2
3
4
$ kubectl apply -f cm-test-01.yaml
$ kubectl logs cm-test-pod-01 | grep SPECIAL
SPECIAL_HOW_KEY=very
SPECIAL_SAM_KEY=charm

用 ConfigMap 设置命令行参数

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
# cat cm-test-02.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: config-01
namespace: default
data:
a: b
c: d
---
apiVersion: v1
kind: Pod
metadata:
name: cm-test-pod-02
spec:
containers:
- name: test-container
image: arminto/my_nginx:v1
command: ["/bin/sh","-c","echo $(KEY-01) $(KEY-02)"]
env:
- name: KEY-01
valueFrom:
configMapKeyRef:
name: config-01
key: a
- name: KEY-02
valueFrom:
configMapKeyRef:
name: config-01
key: c
restartPolicy: Never
1
2
$ kubectl logs cm-test-pod-02
b d

通过数据卷插件使用ConfigMap

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
# cat cm-test-03.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
name: config-02
namespace: default
data:
how: HOW
sam: SAM
---
# 在数据卷里面使用这个 ConfigMap,有不同的选项。最基本的就是将文件填入数据卷,在这个文件中,键就是文件名,键值就是文件内容。
apiVersion: v1
kind: Pod
metadata:
name: test-pod-02
spec:
containers:
- name: test-container
image: arminto/my_nginx:v1
command: ["/bin/sh","-c","sleep 36000"]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: config-02
restartPolicy: Never
1
2
3
4
5
6
7
8
9
10
$ kubectl exec -it test-pod-02 -- ls -lh /etc/config
total 0
lrwxrwxrwx 1 root root 10 Mar 9 08:44 how -> ..data/how
lrwxrwxrwx 1 root root 10 Mar 9 08:44 sam -> ..data/sam

$ kubectl exec -it test-pod-02 -- cat /etc/config/how
HOW

$ kubectl exec -it test-pod-02 -- cat /etc/config/sam
SAM

7.1.3 ConfigMap 的热更新

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: ConfigMap
metadata:
name: log-config
namespace: default
data:
log_level: INFO
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: arminto/my_nginx:v1
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: log-config
1
2
$ kubectl exec -it my-nginx-7b6584d96d-hpbml -- cat /etc/config/log_level
INFO

修改 ConfigMap,将其INFO改为DEBUG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ kubectl edit configmap log-config
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
log_level: DEBUG
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"log_level":"DEBUG"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"log-config","namespace":"default"}}
creationTimestamp: "2021-03-09T09:04:08Z"
name: log-config
namespace: default
resourceVersion: "36210"
selfLink: /api/v1/namespaces/default/configmaps/log-config
uid: 8e2f15fc-cc05-414b-ad2f-9597cacf7c9a

修改log_level的值为DEBUG等待大概 10 秒钟时间,再次查看环境变量的值

1
2
$ kubectl exec -it my-nginx-7b6584d96d-hpbml -- cat /etc/config/log_level
DEBUG

7.2 Secret

Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec中。

并且,Secret 可以以 Volume 或者 环境变量的方式使用。

Secret 有三种类型

  • Service Account
  • Opaque
  • kubernetes.io/dockerconfigjson

7.2.1 Service Account

大致了解一下即可,Service Account 用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod的/run/secrets/kubernetes.io/serviceaccount目录中。

PS : 只有与 apiserver 组件进行交互的 pod 才会有如下的 证书、命名空间、tekon密钥。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ kubectl exec -it kube-proxy-2x7r8 -n kube-system -- ls -lh /run/secrets/kubernetes.io/serviceaccount
total 0
lrwxrwxrwx 1 root root 13 Mar 10 02:50 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Mar 10 02:50 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Mar 10 02:50 token -> ..data/token

# 这几个文件是 default-token 提供的,而 default-token 是 k8s 默认为每一个namespace 创建的,用于Service Account
$ kubectl get secret
NAME TYPE DATA AGE
default-token-c6vqw kubernetes.io/service-account-token 3 46d

$ kubectl describe secret default-token-c6vqw
…………………………………………………………………………
Data
====
ca.crt: 1025 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tYzZ2cXciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjJjNjlmMjkzLTcwNTQtNDY4My05ODNmLTljYmQyNTBlZTQ1ZiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.WdQ32P2ndcKUNbyrsKouTgBEVQf8Smq1mSd9zRpBou1KN2DUJ6UPWHvMdDlqwvN2CACOWZcri0eN8NABvNEW6cQMv7O7-GoVsuv5HLDxmO1IC1tTVUr8d-uBxULiSiQZ_Mj2LK1QKYNAHzVYRlEozzI3wZAOWBDqA7ZFVpXRQMES0lzlRc9ETZqQmxIHgF_hyTzpsRaLkR480vppOTORejZGrm_wiSvYVKKvNe3H35TRtHARCZDlGv7DAickld2rnYuKSyLdZz29CmDiE6L0ZyzTCMgPalNkmjcuij28XG19I4GLio7IiS5ZMDrQsmr_4t3G449PPSiH_0C78LICuA

7.2.2 Opaque Secret

base64编码格式的Secret,用来存储密码、密钥等。

Opaque 类型的数据是一个 map 类型,要求 value 是 base64 编码格式。

1、创建 Opaque-Secret

1
2
3
4
5
6
# base64 -d 选项为解密
$ echo -n "admin" | base64
YWRtaW4=

$ echo -n "1f2d1e2e67df" | base64
MWYyZDFlMmU2N2Rm
1
2
3
4
5
6
7
8
9
# cat opaque-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: op-secret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
1
2
3
4
5
6
7
8
9
10
11
$ kubectl get secret
NAME TYPE DATA AGE
default-token-c6vqw kubernetes.io/service-account-token 3 46d
op-secret Opaque 2 11s

$ kubectl describe secret op-secret
…………………………………………………………………………
Data
====
password: 12 bytes
username: 5 bytes

2、使用方式

  • 将 Secret 挂载到 Volume 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cat volume-secret.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
name: seret-test
name: seret-test
spec:
volumes:
- name: secrets
secret:
secretName: op-secret
containers:
- image: arminto/my_nginx:v1
name: db
volumeMounts:
- name: secrets
mountPath: '/etc/secrets'
readOnly: true
1
2
3
4
5
6
7
8
9
10
11
$ kubectl exec -it seret-test -- ls -l /etc/secrets
total 0
lrwxrwxrwx 1 root root 15 Mar 10 03:25 password -> ..data/password
lrwxrwxrwx 1 root root 15 Mar 10 03:25 username -> ..data/username

# 并且 secret 是会自己进行解密的
$ kubectl exec -it seret-test -- cat /etc/secrets/password
1f2d1e2e67df

$ kubectl exec -it seret-test -- cat /etc/secrets/username
admin
  • 将 Secret 导出到环境变量中
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
# cat env-secret.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: pod-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: pod-deployment
spec:
containers:
- name: pod-1
image: arminto/my_nginx:v1
ports:
- containerPort: 80
env:
- name: TEST_USER
valueFrom:
secretKeyRef:
name: op-secret
key: username
- name: TEST_PASSWORD
valueFrom:
secretKeyRef:
name: op-secret
key: password
1
2
3
4
5
$ kubectl exec -it pod-deployment-7cfd69dcf7-222q2 -- /bin/sh
/ # echo $TEST_USER
admin
/ # echo $TEST_PASSWORD
1f2d1e2e67df

7.2.3 dockerconfigjson

用来存储私有 docker registry 的认证信息。

1、使用 Kuberctl 创建 docker registry 认证的 secret(注:针对私有仓库)

1
2
3
4
5
$ kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD

# --docker-server 仓库地址
# --docker-username 仓库用户
# --docker-password 仓库密码

2、在创建 Pod 的时候,通过 imagePullSecrets 来引用刚创建的 myregistrykey

1
2
3
4
5
6
7
8
9
10
11
# cat key-secret.yaml 
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: foo
image: hub.lemon.com/library/my_nginx:v1
imagePullSecrets:
- name: myregistrykey

7.3 Volume

容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件。Kubernetes 中的Volume抽象就很好的解决了这些问题。

Kubernetes 中的卷有明确的寿命 —— 与封装它的 Pod 相同。所f以,卷的生命比 Pod 中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当 Pod 不再存在时,卷也将不复存在。也许更重要的是,Kubernetes支持多种类型的卷,Pod 可以同时使用任意数量的卷。

Kubernetes 支持以下类型的卷:

  • awsElasticBlockStore、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir
  • fc、flocker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs
  • persistentVolumeClaim、projected、portworxVolume、quobyte、rbd、scaleIO、secret
  • storageos、vsphereVolume

比较常用的两类 emptyDir、hostPath

7.3.1 emptyDir

当 Pod 被分配给节点时,首先创建emptyDir卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。Pod 中的容器可以读取和写入emptyDir卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上。当出于任何原因从节点中删除 Pod 时,emptyDir中的数据将被永久删除。

emptyDir的用法有:

  • 暂存空间,例如用于基于磁盘的合并排序
  • 用作长时间计算崩溃恢复时的检查点
  • Web服务器容器提供数据时,保存内容管理器容器提取的文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# cat emptyDir.yaml 
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- name: test-container01
image: arminto/my_nginx:v1
volumeMounts:
- mountPath: /cache01
name: cache-volume
- name: test-container02
image: tomcat:latest
volumeMounts:
- mountPath: /cache02
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
1
2
3
4
5
6
7
8
9
10
11
12
$ kubectl exec -it test-pd -c test-container01 -- /bin/sh
/ # cd /cache01
/cache01 # hostname >> test.log
/cache01 # cat test.log
test-pd

$ kubectl exec -it test-pd -c test-container02 -- /bin/sh
# cd /cache02
# ls
test.log
# cat test.log
test-pd

7.3.2 hostPath

hostPath卷将主机节点的文件系统中的文件或目录挂载到集群中。

hostPath 的用途如下:

  • 运行需要访问 Docker 内部的容器;使用 /var/lib/docker 的 hostPath
  • 在容器中运行 cAdvisor;使用 /dev/cgroups 的 hostPath
  • 允许 pod 指定给定的 hostPath 是否应该在 pod 运行之前存在,是否应该创建,以及它应该以什么形式存在

除了所需的path属性之外,用户还可以为hostPath卷指定type

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

使用这种卷类型是请注意,因为:

  • 由于每个节点上的文件都不同,具有相同配置(例如从 podTemplate 创建的)的 pod 在不同节点上的行为可能会有所不同。
  • 当 Kubernetes 按照计划添加资源感知调度时,将无法考虑hostPath使用的资源。
  • 在底层主机上创建的文件或目录只能由 root 写入。您需要在特权容器中以 root 身份运行进程,或修改主机上的文件权限以便写入hostPath卷。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: h-pod
spec:
containers:
- image: arminto/my_nginx:v1
name: test-container
volumeMounts:
- mountPath: /hv
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory
1
2
3
4
5
6
7
8
9
10
$ kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE
h-pod 0/1 ContainerCreating 0 7s <none> k8s-node-192.168.2.22

$ hostname
k8s-node-192.168.2.22
$ echo 'hv test' > /data/h-test.log

$ kubectl exec -it h-pod -- cat /hv/h-test.log
hv test

7.4 Persistent Volume(PV)

PersistentVolume(简称PV) 是 Volume 之类的卷插件,也是集群中的资源,但独立于Pod的生命周期(即不会因Pod删除而被删除),不归属于某个Namespace。

PersistentVolumeClaim(简称PVC)是用户存储的请求,PVC消耗PV的资源,可以请求特定的大小和访问模式,需要指定归属于某个Namespace,在同一个Namespace 的 Pod才可以指定对应的PVC。

PV (持久化卷),是对底层的共享存储的一种抽象,PV 由管理员进行创建和配置, 它和具体的底层的共享存储技术的实现方式有关,比如Ceph、GlusterFS、NFS等,都是通过插件机制完成与共享存储的对接。

PVC (持久化卷声明),PVC 是用户存储的一种声明,PVC 和 Pod 比较类型,Pod 是消耗节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 的内存,而 PVC 可以请求特定的存储空间和访问模式。对于真正存储的用户不需要关心底层的存储实现细节,只需要直接使用PVC即可。

但是通过PVC请求一定的存储空间也很有可能不足以满足对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求也能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes又为我们引入了一个新的资源对象: StorageClass,通过StorageClass的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据StorageClass的描述就可以非常直观的知道各种存储资源特性了,这样就可以根据应用的特性去申请合适的存储资源了。

7.4.1 PV 和 PVC的生命周期

PV 可以看作可用的存储资源,PVC则是对存储资源的需求,PV 和 PVC的互相关系遵循如下图

image-20210311104829767

7.4.2 资源供应 (Provisioning)

Kubernetes支持两种资源的供应模式:静态模式(Staic)和动态模式(Dynamic)。资源供应的结果就是创建好的PV。

  • 静态模式:集群管理员手工创建许多 PV,在定义 PV 时需要将后端存储的特性进行设置。
  • 动态模式:集群管理员无须手工创建 PV,而是通过 StorageClass 的设置对后端存储进行描述,标记为某种 “类型(Class)”。此时要求 PVC 对存储的类型进行声明,系统将自动完成 PV 的创建及 PVC 的绑定。
    • PVC 可以声明 Class 为””,说明该 PVC 禁止使用动态模式。

1、静态资源下,通过PV和PVC完成绑定,并供Pod使用的存储管理机制

image-20210311105424072

2.动态资源下,通过StorageClass和PVC完成资源动态绑定 (系统自动生成PV,并供Pod使用的存储管理机制)

image-20210311105449805

7.4.3 资源绑定 (Binding)

在用户定义好PVC后,系统将根据PVC对存储资源的请求 (存储空间和访问模式)在已存在的PV中选择一个满足PVC要求的PV,一旦找到,就将该PV与用户定义的PVC进行绑定,然后用户的应用就可以使用这个PVC了。如果系统中没有满足PVC要求的PV,PVC则会无限期处于Pending状态,直到等到系统管理员创建了一个符合要求的PV。PV一旦绑定在某个PVC上,就被这个PVC独占,不能再与其他PVC进行绑定了。在这种情况下,当PVC申请的存储空间比PV的少时,整个PV的空间都能够为PVC所用,可能会造成资源的浪费。如果资源供应使用的是动态模式,则系统在PVC找到合适的StorageClass后,将会自动创建PV并完成PVC的绑定。

7.4.4 资源使用 (Using)

Pod 使用volume的定义, 将 PVC 挂载到容器内的某个路径进行使用。volume 的类型为 persistentVoulumeClaim , 在容器应用挂载了一个 PVC 后, 就能被持续独占使用。

不过, 多个Pod可以挂载同一个PVC, 应用程序需要考虑多个实例共同访问一块存储空间的问题。

7.4.5 资源释放 (Releasing)

当用户对存储资源使用哪个完毕后,用户可以删除PVC,与该PVC绑定的PV将会被标记为已释放,但还不能立刻与其他PVC进行绑定。通过之前PVC写入的数据可能还留在存储设备上,只有在清除之后该PV才能继续使用。

7.4.6 资源回收 (Reclaiming)

对于PV,管理员可以设定回收策略(Reclaim Policy)用于设置与之绑定的PVC释放资源之后,对于遗留数据如何处理。只有PV的存储空间完成回收,才能供新的PVC绑定和使用。

7.4.7 持久化卷声明的保护

PVC 保护的目的是确保由 pod 正在使用的 PVC 不会从系统中移除,因为如果被移除的话可能会导致数据丢失,当启用 PVC 保护 alpha 功能时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除。PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用。

7.4.8 PV 的类型 及 访问模式

PersistentVolume 类型以插件形式实现。以下仅列部分常用类型:

  • GCEPersistentDisk
  • AWSElasticBlockStore
  • NFS
  • RBD (Ceph Block Device)
  • CephFS、Glusterfs

PV 的访问模式 有三种 ReadWriteOnce 、 ReadOnlyMany 、 ReadWriteMany

PersistentVolume 可以以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个PV 的访问模式都将被设置为该卷支持的特定模式。例如,NFS 可以支持多个读/写客户端,但特定的 NFS PV 可能以只读方式导出到服务器上。每个 PV 都有一套自己的用来描述特定功能的访问模式。

  • ReadWriteOnce——该卷可以被单个节点以读/写模式挂载(命令行缩写:RWO)
  • ReadOnlyMany——该卷可以被多个节点以只读模式挂载(命令行缩写:ROX)
  • ReadWriteMany——该卷可以被多个节点以读/写模式挂载(命令行缩写:RWX)

一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。以下只列举部分常用插件

Volume 插件 ReadWriteOnce ReadOnlyMany ReadWriteMany
AWSElasticBlockStore - -
CephFS
GCEPersistentDisk -
Glusterfs
HostPath - -
NFS
RBD -

7.4.9 PV 的回收策略 及 阶段状态

回收策略包括

  • Retain(保留)——手动回收
  • Recycle(回收)——基本擦除( rm -rf /thevolume/* )【ps 在新版本中该策略已被弃用】
  • Delete(删除)——关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除。

当前,只有 NFS 和 HostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略。

PV 可以处于以下的某种状态

  • Available(可用)——一块空闲资源还没有被任何声明绑定
  • Bound(已绑定)——卷已经被声明绑定
  • Released(已释放)——声明被删除,但是资源还未被集群重新声明
  • Failed(失败)——该卷的自动回收失败

命令行会显示绑定到 PV 的 PVC 的名称。

7.4.10 PV 的实验演练 - NFS

1、安装 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
28
# 所有的节点都需要安装 nfs-utils 和 rpcbind
$ yum install -y nfs-common nfs-utils rpcbind
$ mkdir -p /nfs01 /nfs02 /nfs03 /nfs04
$ chmod 777 /nfs01 /nfs02 /nfs03 /nfs04
$ chown nfsnobody /nfs01 /nfs02 /nfs03 /nfs04
$ cat /etc/exports
/nfs01 *(rw,no_root_squash,no_all_squash,sync)
/nfs02 *(rw,no_root_squash,no_all_squash,sync)
/nfs03 *(rw,no_root_squash,no_all_squash,sync)
/nfs04 *(rw,no_root_squash,no_all_squash,sync)
$ systemctl start rpcbind nfs

# 登陆k8s任意一台节点上测试下nfs
$ showmount -e 192.168.245.44
Export list for 192.168.245.44:
/nfs01 *
/nfs02 *
/nfs03 *
/nfs04 *
$ mkdir /test
$ mount -t nfs 192.168.245.44:/nfs01 /test/
$ cd /test/
$ touch tset.txt
$ echo lemon > test.txt
$ cat test.txt
lemon
$ umount /test
$ rm -rf /test

2、部署 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
# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs01
server: 192.168.245.44
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv2
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs02
server: 192.168.245.44
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv3
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs03
server: 192.168.245.44
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv4
spec:
capacity:
storage: 30Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: static
nfs:
path: /nfs04
server: 192.168.245.44

3、创建服务并使用 PVC

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
# cat pvc.yaml 
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: arminto/my_nginx:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs"
resources:
requests:
storage: 1Gi

image-20210311162007092

1
2
3
4
5
6
7
8
9
10
$ echo 'web-0' > /nfs01/index.html
$ echo 'web-1' > /nfs02/index.html
$ echo 'web-2' > /nfs03/index.html

$ curl 10.244.2.24
web-0
$ curl 10.244.1.26
web-1
$ curl 10.244.1.27
web-2

7.4.11 关于 StatefulSet 的说明

  • 匹配 Pod name ( 网络标识 ) 的模式为:$(statefulset名称)-$(序号),比如上面的示例:web-0,web-1,web-2 。
1
2
3
4
5
6
$ kubectl get pod 
NAME READY STATUS RESTARTS AGE
h-pod 1/1 Running 0 23h
web-0 1/1 Running 0 48m
web-1 1/1 Running 0 48m
web-2 1/1 Running 0 48m
  • StatefulSet 为每个 Pod 副本创建了一个 DNS 域名,这个域名的格式为: $(podname).(headless servername),也就意味着服务间是通过Pod域名来通信而非 Pod IP,因为当Pod所在Node发生故障时, Pod 会被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化。
1
2
3
4
5
6
7
$ kubectl exec -it h-pod -- /bin/sh

/ # ping -c 3 web-0.nginx
PING web-0.nginx (10.244.2.24): 56 data bytes
64 bytes from 10.244.2.24: seq=0 ttl=64 time=0.137 ms
64 bytes from 10.244.2.24: seq=1 ttl=64 time=0.056 ms
64 bytes from 10.244.2.24: seq=2 ttl=64 time=0.076 ms
  • StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的 FQDN 为:$(servicename).$(namespace).svc.cluster.local,其中,“cluster.local” 指的是集群的域名。
1
2
3
4
5
6
7
8
9
$ kubectl get pod -owide -n kube-system | grep coredns
coredns-5c98db65d4-4v4kd 1/1 Running 13 47d 10.244.1.17
coredns-5c98db65d4-9k5xw 1/1 Running 7 47d 10.244.0.9

$ dig -t A nginx.default.svc.cluster.local. @10.244.0.9
;; ANSWER SECTION:
nginx.default.svc.cluster.local. 11 IN A 10.244.2.24
nginx.default.svc.cluster.local. 11 IN A 10.244.1.26
nginx.default.svc.cluster.local. 11 IN A 10.244.1.27
  • 根据 volumeClaimTemplates,为每个 Pod 创建一个 pvc,pvc 的命名规则匹配模式:(volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Podname=web-[0-2],因此创建出来的 PVC 是 www-web-0、www-web-1、www-web-2 。
1
2
3
4
5
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfspv1 1Gi RWO nfs 173m
www-web-1 Bound nfspv2 10Gi RWO nfs 172m
www-web-2 Bound nfspv3 20Gi RWO nfs 172m
  • 删除 Pod 不会删除其 pvc,手动删除 pvc 将自动释放 pv

Statefulset的启停顺序

  • 有序部署:部署StatefulSet时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1)并且,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ kubectl get pv 
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
nfspv1 1Gi RWO Retain Available nfs
nfspv2 10Gi RWO Retain Available static
nfspv3 20Gi RWO Retain Available slow

$ kubectl apply -f pvc.yaml
service/nginx created
statefulset.apps/web created

# 这里可以看到第二个 pod 一直处于 pending 状态,导致第三个 pod 都没有创建出来
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 19s
web-1 0/1 Pending 0 16s
  • 有序删除:当Pod被删除时,它们被终止的顺序是从N-1到0。
1
2
$ kubectl get pod -owide -n default -w
$ kubectl delete statefulset web -n default
  • 有序扩展:当对 Pod 执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态。
1
$ kubectl scale statefulset/web --replicas=4

StatefulSet使用场景

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于 PVC 来实现。
  • 稳定的网络标识符,即 Pod 重新调度后其 PodName 和 HostName 不变。
  • 有序部署,有序扩展,基于 init containers 来实现。
  • 有序收缩。

7.4.12 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
# statefulset > svc > pvc > 使用 edit 删除 pv 卷上的 pvc 记录
$ kubectl delete -f pvc.yaml
$ kubectl delete pvc www-web-0 www-web-1 www-web-2

# 虽然已经删除了 pvc,但实际上还并没有释放 pv,因为在pv的记录信息里面还是有使用者信息的,需要手工删除后才会释放
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
nfspv1 1Gi RWO Retain Released default/www-web-0 nfs
nfspv2 10Gi RWO Retain Released default/www-web-1 nfs
nfspv3 20Gi RWO Retain Released default/www-web-2 nfs
nfspv4 30Gi RWX Retain Available static

# 手动删除这一段,pv 就会释放资源了, 其他的 pod 也就可以继续使用这些 pv 卷了
$ kubectl get pv nfspv1 -o yaml
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: www-web-0
namespace: default
resourceVersion: "69260"
uid: e175a86b-aa2c-4900-a7c4-faa1865315ab

$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS
nfspv1 1Gi RWO Retain Available nfs
nfspv2 10Gi RWO Retain Available nfs
nfspv3 20Gi RWO Retain Available nfs
nfspv4 30Gi RWX Retain Available static

# 如果这些卷及数据都不想要了的话,直接在对应的nfs机器上删除挂在点上的数据,并在master上删除pv卷 kubectl delete pv xx

五、K8S集群调度

Scheduler 是 kubernetes 的调度器,主要任务是把定义的 pod 分配到集群的节点上。有四点需要考虑:

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

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

1、调度说明

调度过程

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

Predicate 有一系列的算法可以使用:

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

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

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

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

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

自定义调度器

除了 kubernetes 自带的调度器,你也可以编写自己的调度器。通过spec:schedulername参数指定调度器的名字,可以为 pod 选择某个调度器进行调度。比如下面的 pod 选择my-scheduler进行调度,而不是默认的default-scheduler:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: annotation-second-scheduler
labels:
name: multischeduler-example
spec:
schedulername: my-scheduler
containers:
- name: pod-with-second-annotation-container
image: arminto/my_nginx:v1

2、调度亲和性

一般情况下 POD 是通过集群自动调度选择某个节点的,默认情况下调度器考虑的是资源足够,并且负载尽量平均,但是有的时候需要能够更加细粒度的去控制 POD 的调度,比如内部的一些服务 gitlab 之类的也是跑在Kubernetes集群上的,就不希望对外的一些服务和内部的服务跑在同一个节点上了,害怕内部服务对外部的服务产生影响;有的时候两个服务直接交流比较频繁,又希望能够将这两个服务的 POD 调度到同样的节点上。

这就需要用到 Kubernetes 里面的一个概念:亲和性,亲和性主要分为两类:nodeAffinitypodAffinity

节点亲和性(nodeAffinity)

键值运算关系

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

pod.spec.nodeAffinity

  • requiredDuringSchedulingIgnoredDuringExecution:硬策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
name: nodeaffinity01
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: arminto/my_nginx:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node-192.168.2.55
1
2
3
4
5
$ kubectl get nodes --show-labels   # 查看节点的LABELS

$ kubectl get pod -owide # 由于没有k8s-node-192.168.2.55值的key,而且还是硬策略,所以pod会一直pending中。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nodeaffinity01 0/1 Pending 0 5s <none> <none> <none> <none>
  • preferredDuringSchedulingIgnoredDuringExecution:软策略
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: nodeaffinity02
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: arminto/my_nginx:v1
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node-192.168.2.55
1
2
3
4
$ kubectl get pod -owide     # 软策略
NAME READY STATUS RESTARTS AGE IP NODE
nodeaffinity01 0/1 Pending 0 3m46s <none> <none>
nodeaffinity02 1/1 Running 0 5s 10.244.4.8 k8s-node-192.168.2.33

Tips:硬策略和软策略是可以合并一起使用的,但是必须要先满足硬策略,才能在满足软策略。

Pod 亲和性(podAffinity)

pod.spec.affinity.podAffinity/podAntiAffinity

  • preferredDuringSchedulingIgnoredDuringExecution:软策略
  • requiredDuringSchedulingIgnoredDuringExecution:硬策
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
apiVersion: v1
kind: Pod
metadata:
name: test
labels:
app: lemon
spec:
containers:
- name: lemon
image: arminto/my_nginx:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-node-192.168.2.33
---
apiVersion: v1
kind: Pod
metadata:
name: lemon
labels:
app: lemon
spec:
containers:
- name: lemon
image: arminto/my_nginx:v1
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- lemon
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: tools
operator: In
values:
- armin
topologyKey: kubernetes.io/hostname
1
2
3
4
$ kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE
lemon 1/1 Running 0 4s 10.244.4.12 k8s-node-192.168.2.33
test 1/1 Running 0 4s 10.244.4.11 k8s-node-192.168.2.33

亲和性/反亲和性调度策略比较如下:

调度策略 匹配标签 操作符 拓扑域支持 调度目标
nodeAffinity 主机 In, NotIn, Exists,DoesNotExist, Gt, Lt 指定主机
podAffinity POD In, NotIn, Exists,DoesNotExist POD与指定POD同一拓扑域
podAnitAffinity POD In, NotIn, Exists,DoesNotExist POD与指定POD不在同一拓扑域

Tips:拓扑域 就是节点的意思

3、调度污点

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

Taint 和 Toleration

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

污点(Taint)

1、污点 ( Taint ) 的组成

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

每个污点的组成如下:

1
key=value:effect

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

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

2、污点的设置、查看和去除

1
2
3
4
5
6
7
8
# 设置污点
$ kubectl taint nodes node1 key1=value1:NoExecute

# 节点说明中,查找 Taints 字段
$ kubectl describe node node-name

# 去除污点
$ kubectl taint nodes node1 key1:NoExecute-

容忍(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: "NoExecute"
tolerationSeconds: 3600
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key2"
operator: "Exists"
effect: "PreferNoSchedule"
  • 其中 key, vaule, effect 要与 Node 上设置的 taint 保持一致

  • operator 的值为 Exists 将会忽略 value 值

  • tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Node 上继续保留运行的时间,会一直循环

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

1
2
tolerations:
- operator: "Exists"

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

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

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

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

4、固定节点

指定调度节点

1、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: web01
spec:
replicas: 5
template:
metadata:
labels:
app: web01
spec:
nodeName: k8s-node-192.168.2.22
containers:
- name: web01
image: arminto/my_nginx:v1
ports:
- containerPort: 80

2、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: web02
spec:
replicas: 3
template:
metadata:
labels:
app: web02
spec:
nodeSelector:
k1: v1
containers:
- name: web02
image: arminto/my_nginx:v1
ports:
- containerPort: 80
1
$ kubectl label node k8s-node-192.168.2.33 k1=v1

六、K8S 集群安全

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

image-20210406174845095

1、集群认证

Authentication

  • HTTP Token 认证:通过一个 Token 来识别合法用户
    • HTTP Token 的认证是用一个很长的特殊编码方式的并且难以被模仿的字符串 - Token 来表达客户的一种方式。Token 是一个很长的很复杂的字符串,每一个 Token 对应一个用户名存储在 API Server 能访问的文件中。当客户端发起 API 调用请求时,需要在 HTTP Header 里放入 Token。
  • HTTP Base 认证:通过用户名+密码的方式认证
    • 用户名+:+密码用 BASE64 算法进行编码后的字符串放在 HTTP Request 中的 HeatherAuthorization 域里发送给服务端,服务端收到后进行编码,获取用户名及密码
  • 最严格的 HTTPS 证书认证:基于 CA 根证书签名的客户端身份认证方式

HTTPS 证书认证

image-20210406175149666

需要认证的节点

image-20210406175424225

两种类型

  • 两种类型Kubenetes 组件对 API 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 访问 API 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 访问API Server的认证问题。

Secret 与 SA 的关系

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

  • oken是使用 API Server 私钥签名的 JWT。用于访问API 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。

总结

image-20210406175833477

2、集群鉴权

3、准入控制

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

官方文档上有一份针对不同版本的准入控制器推荐列表,其中最新的 1.14 的推荐列表是:

1
NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota

列举几个插件的功能:

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