# Kubernetes

[TOC]

VPC 私有网络,不同 VPC 之间相互隔离

k8s 大规模容器编排系统

master/node 模式

# k8s 特性

  • 服务发现和负载均衡
    Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器,如果进入容器的流量很大, Kubernetes 可以负载均衡并分配网络流量,从而使部署稳定。
  • 存储编排
    Kubernetes 允许你自动挂载你选择的存储系统,例如本地存储、公共云提供商等。
  • 自动部署和回滚
    你可以使用 Kubernetes 描述已部署容器的所需状态,它可以以受控的速率将实际状态 更改为期望状态。例如,你可以自动化 Kubernetes 来为你的部署创建新容器, 删除现有容器并将它们的所有资源用于新容器。
  • 自动完成装箱计算
    Kubernetes 允许你指定每个容器所需 CPU 和内存(RAM)。 当容器指定了资源请求时,Kubernetes 可以做出更好的决策来管理容器的资源。
  • 自我修复
    Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的 运行状况检查的容器,并且在准备好服务之前不将其通告给客户端。
  • 密钥与配置管理
    Kubernetes 允许你存储和管理敏感信息,例如密码、OAuth 令牌和 ssh 密钥。 你可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥。

# 架构

主节点 (master) + 工作节点 (worker)

K8s 集群至少需要一个主节点 (Master) 和多个工作节点 (Worker),Master 节点是集群的控制节点,负责整个集群的管理和控制,主节点主要用于暴露 API,调度部署和节点的管理。工作节点主要是运行容器的。

Kubernetes 组件

多 master 节点高可用架构

image-20230423162509705

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kubectl:管理K8s的命令行工具,可以操作K8s中的资源对象。

etcd: 是一个高可用的键值数据库,存储K8s的资源状态信息和网络信息的,etcd中的数据变更是通过api server进行的。

apiserver: 提供K8s api,是整个系统的对外接口,提供资源操作的唯一入口,供客户端和其它组件调用,提供了K8s各类资源对象(pod,deployment,Service等)的增删改查,是整个系统的数据总线和数据中心,并提供认证、授权、访问控制、API注册和发现等机制,并将操作对象持久化到etcd中。相当于“营业厅”。

scheduler:负责K8s集群中pod的调度的 , scheduler通过与apiserver交互监听到创建Pod副本的信息后,它会检索所有符合该Pod要求的工作节点列表,开始执行Pod调度逻辑。调度成功后将Pod绑定到目标节点上,相当于“调度室”。

controller-manager:与apiserver交互,实时监控和维护K8s集群的控制器的健康情况,对有故障的进行处理和恢复,相当于“大总管”。

kubelet: 每个Node节点上的kubelet定期就会调用API Server的REST接口报告自身状态,API Server接收这些信息后,将节点状态信息更新到etcd中。kubelet也通过API Server监听Pod信息,从而对Node机器上的POD进行管理,如创建、删除、更新Pod

kube-proxy:提供网络代理和负载均衡,是实现service的通信与负载均衡机制的重要组件,kube-proxy负责为Pod创建代理服务,从apiserver获取所有service信息,并根据service信息创建代理服务,实现service到Pod的请求路由和转发,从而实现K8s层级的虚拟转发网络,将到service的请求转发到后端的pod上。

calico:网络插件,提供IP,策略隔离

coredns:域名解析

# 资源对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Pod是Kubernetes中的最小调度单元,当指派容器时,容器实际上并不会指派到物理硬件上,容器会被分配到一个Pod里。Pod代表集群上正在运行的一个进程,一个Pod封装一个容器(也可以封装多个容器),Pod里的容器共享存储、网络等。也就是说,应该把整个pod看作虚拟机,然后每个容器相当于运行在虚拟机的进程。

Replicaset
Kubernetes中的副本控制器,管理Pod,使pod副本的数量始终维持在预设的个数。

Deployment管理Replicaset和Pod的副本控制器,Deployment可以管理多个Replicaset,是比Replicaset更高级的控制器,也即是说在创建Deployment的时候,会自动创建Replicaset,由Replicaset再创建Pod,Deployment能对Pod扩容、缩容、滚动更新和回滚、维持Pod数量。

Service
在kubernetes中,Pod是有生命周期的,如果Pod重启IP很有可能会发生变化。如果我们的服务都是将Pod的IP地址写死,Pod的挂掉或者重启,和刚才重启的pod相关联的其他服务将会找不到它所关联的Pod,为了解决这个问题,在kubernetes中定义了service资源对象,Service 定义了一个服务访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,service是一组Pod的逻辑集合,这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector实现的。

Statefulset

Job & CronJob

Ingress

Configmap和Secret


开发代码 -> 提交代码到代码仓库 ->Jenkins 调 K8s API-> 动态生成 Jenkins Slave Pod->Slave Pod 拉取 git 上的代码 -> 编译代码 -> 打包镜像 -> 推送镜像到镜像仓库 harbor 或者 docker hub-> 通过 K8s 编排服务发布到测试、生产平台 -> Slave Pod 工作完成之后自动删除 > 通过 Ingress 发布服务。

# kubeadm 安装 k8s 多 master 高可用集群

kubeadm 是官方提供的开源工具,是一个开源项目,用于快速搭建 kubernetes 集群,目前是比较方便和推荐使用的。kubeadm init 以及 kubeadm join 这两个命令可以快速创建 kubernetes 集群。Kubeadm 初始化 k8s,所有的组件都是以 pod 形式运行的,具备故障自恢复能力。

kubeadm 是工具,可以快速搭建集群,也就是相当于用程序脚本帮我们装好了集群,属于自动部署,简化部署操作,自动部署屏蔽了很多细节,使得对各个模块感知很少,如果对 k8s 架构组件理解不深的话,遇到问题比较难排查。

kubeadm 适合需要经常部署 k8s,或者对自动化要求比较高的场景下使用。

二进制:在官网下载相关组件的二进制包,如果手动安装,对 kubernetes 理解也会更全面。

Kubeadm 和二进制都适合生产环境,在生产环境运行都很稳定,具体如何选择,可以根据实际项目进行评估。

1
2
./configure --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-compat --with-debug --with-file-aio --with-google_perftools_module --prefix= --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_degradation_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_mp4_module --with-http_perl_module=dynamic --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-http_xslt_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-threads --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E' --with-stream 

1
2
3
4
5
auto eth0
iface eth0 inet static
address 192.168.13.198
gateway 192.168.13.2
netmask 255.255.255.0

# 安装

1. 安装 docker

2. 安装 kubelet

3. 安装 kubectl

4. 安装 kubeadm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
预配环境:
#各个机器设置自己的域名
hostnamectl set-hostname xxxx


# 将 SELinux 设置为 permissive 模式(相当于将其禁用)
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

#关闭swap
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab

#允许 iptables 检查桥接流量
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
br_netfilter
EOF

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system

安装 kubelet、kubeadm、kubectl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF


sudo yum install -y kubelet-1.20.9 kubeadm-1.20.9 kubectl-1.20.9 --disableexcludes=kubernetes

sudo systemctl enable --now kubelet

安装 kubelet、kubeadm、kubectl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo tee ./images.sh <<-'EOF'
#!/bin/bash
images=(
kube-apiserver:v1.20.9
kube-proxy:v1.20.9
kube-controller-manager:v1.20.9
kube-scheduler:v1.20.9
coredns:1.7.0
etcd:3.4.13-0
pause:3.2
)
for imageName in ${images[@]} ; do
docker pull registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/$imageName
done
EOF

chmod +x ./images.sh && ./images.sh

初始化主节点

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
#所有机器添加master域名映射,以下需要修改为自己的
echo "172.23.142.216 k8s-master" >> /etc/hosts



#主节点初始化
kubeadm init \
--apiserver-advertise-address=172.23.142.216 \
--control-plane-endpoint=k8s-master \
--image-repository registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images \
--kubernetes-version v1.20.9 \
--service-cidr=10.96.0.0/16 \
--pod-network-cidr=192.168.0.0/16

#所有网络范围不重叠

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

Alternatively, if you are the root user, you can run:

export KUBECONFIG=/etc/kubernetes/admin.conf

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/

You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:

kubeadm join cluster-endpoint:6443 --token 902kx7.ic9ncjvtuksmf2rw \
--discovery-token-ca-cert-hash sha256:753403dbd24f4cef391a276ad67e8c9c8d91cec64467998426e3021123381b36 \
--control-plane

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

kubeadm join cluster-endpoint:6443 --token 902kx7.ic9ncjvtuksmf2rw \
--discovery-token-ca-cert-hash sha256:753403dbd24f4cef391a276ad67e8c9c8d91cec64467998426e3021123381b36

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
主节点
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

主节点
下载calico
https://docs.tigera.io/archive/v3.22/getting-started/kubernetes/self-managed-onprem/onpremises
curl https://projectcalico.docs.tigera.io/archive/v3.22/manifests/calico.yaml -O

kubectl apply -f calico.yaml

[root@i-taf5qf6u ~]# kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-kube-controllers-57c7b58f66-jq7lm 0/1 ContainerCreating 0 22m
kube-system calico-node-4bttn 1/1 Running 0 22m
kube-system coredns-5897cd56c4-2fhkm 1/1 Running 0 55m
kube-system coredns-5897cd56c4-2l59f 1/1 Running 0 55m
kube-system etcd-master 1/1 Running 0 55m
kube-system kube-apiserver-master 1/1 Running 0 55m
kube-system kube-controller-manager-master 1/1 Running 0 55m
kube-system kube-proxy-d4sgz 1/1 Running 0 55m
kube-system kube-scheduler-master 1/1 Running 0 55m
tigera-operator tigera-operator-57cb64cf85-kqsvh 1/1 Running 0 37m
[root@i-taf5qf6u ~]# kubectl get node
NAME STATUS ROLES AGE VERSION
master Ready control-plane,master 56m v1.20.9

1
2
3
4
5
6
7
8
9
# 查看集群所有节点
kubectl get nodes

# 很久配置文件,给集群创建资源
kubectl appy -f xxx.yml

# 查看集群部署了那些命令
kubectl get pods -A
# 类似于docker ps -a
1
2
3
# worker 加入master
kubeadm join cluster-endpoint:6443 --token 902kx7.ic9ncjvtuksmf2rw \
--discovery-token-ca-cert-hash sha256:753403dbd24f4cef391a276ad67e8c9c8d91cec64467998426e3021123381b36
1
2
# 重新创建新的令牌
kubeadm token create --print-join-command

==== new =====

1
2
3
# 重置kubeadm
kubeadm reset
# 相当于卸载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
# 根据机器上安装了哪些组件区分
# master
apiserver controller-manager scheduler etcd kube-proxy docker calico


# node
coredns
calico
kubelet
kube-proxy
docker


Swap是交换分区,如果机器内存不够,会使用swap分区,但是swap分区的性能较低,k8s设计的时候为了能提升性能,默认是不允许使用交换分区的。Kubeadm初始化的时候会检测swap是否关闭,如果没关闭,那就初始化失败。如果不想要关闭交换分区,安装k8s的时候可以指定--ignore-preflight-errors=Swap来解决。
free -m

1.24之后使用contained

Kubeadm: kubeadm是一个工具,用来初始化k8s集群的
kubelet: 安装在集群所有节点上,用于启动Pod的
kubectl: 通过kubectl可以部署和管理应用,查看各种资源,创建、删除和更新各种组件

[root@master1 ~]# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.13.249:16443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

# 想在其他机器上执行kubevtl需要有/root/.kube/config

kubeadm 在执行安装之前进行了相当细致的环境检测

1) 检查执行 init 命令的用户是否为 root,如果不是 root,直接快速失败(fail fast);
2) 检查待安装的 k8s 版本是否被当前版本的 kubeadm 支持(kubeadm 版本 >= 待安装 k8s 版本);
3) 检查防火墙,如果防火墙未关闭,提示开放端口 10250;
4) 检查端口是否已被占用,6443(或你指定的监听端口)、10257、10259;
5) 检查文件是否已经存在,/etc/kubernetes/manifests/*.yaml;
6) 检查是否存在代理,连接本机网络、服务网络、Pod网络,都会检查,目前不允许代理;
7) 检查容器运行时,使用 CRI 还是 Docker,如果是 Docker,进一步检查 Docker 服务是否已启动,是否设置了开机自启动;
8) 对于 Linux 系统,会额外检查以下内容:
8.1) 检查以下命令是否存在:crictl、ip、iptables、mount、nsenter、ebtables、ethtool、socat、tc、touch
8.2) 检查 /proc/sys/net/bridge/bridge-nf-call-iptables、/proc/sys/net/ipv4/ip-forward 内容是否为 1;
8.3) 检查 swap 是否是关闭状态;
9) 检查内核是否被支持,Docker 版本及后端存储 GraphDriver 是否被支持;
对于 Linux 系统,还需检查 OS 版本和 cgroup 支持程度(支持哪些资源的隔离);
10) 检查主机名访问可达性;
11) 检查 kubelet 版本,要高于 kubeadm 需要的最低版本,同时不高于待安装的 k8s 版本;
12) 检查 kubelet 服务是否开机自启动;
13) 检查 10250 端口是否被占用;
14) 如果开启 IPVS 功能,检查系统内核是否加载了 ipvs 模块;
15) 对于 etcd,如果使用 Local etcd,则检查 2379 端口是否被占用, /var/lib/etcd/ 是否为空目录;
如果使用 External etcd,则检查证书文件是否存在(CA、key、cert),验证 etcd 服务版本是否符合要求;
16) 如果使用 IPv6,
检查 /proc/sys/net/bridge/bridge-nf-call-iptables、/proc/sys/net/ipv6/conf/default/forwarding 内容是否为 1;

完成安装前的配置
1) 在 kube-system 命名空间创建 ConfigMap kubeadm-config,同时对其配置 RBAC 权限;
2) 在 kube-system 命名空间创建 ConfigMap kubelet-config-<version>,同时对其配置 RBAC 权限;
3) 为当前节点(Master)打标记:node-role.kubernetes.io/master=;
4) 为当前节点(Master)补充 Annotation;
5) 如果启用了 DynamicKubeletConfig 特性,设置本节点 kubelet 的配置数据源为 ConfigMap 形式;
6) 创建 BootStrap token Secret,并对其配置 RBAC 权限;
7) 在 kube-public 命名空间创建 ConfigMap cluster-info,同时对其配置 RBAC 权限;
8) 与 apiserver 通信,部署 DNS 服务;
9) 与 apiserver 通信,部署 kube-proxy 服务;
10) 如果启用了 self-hosted 特性,将 Control Plane 转为 DaemonSet 形式运行;
11) 打印 join 语句;

Kubeadm生成的k8s证书内容说明:
一、证书分组
Kubernetes把证书放在了两个文件夹中
/etc/kubernetes/pki
/etc/kubernetes/pki/etcd

二、Kubernetes 集群根证书
Kubernetes 集群根证书CA(Kubernetes集群组件的证书签发机构)
/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/ca.key

以上这组证书为签发其他Kubernetes组件证书使用的根证书, 可以认为是Kubernetes集群中证书签发机构之一

由此根证书签发的证书有:
1、kube-apiserver apiserver证书
/etc/kubernetes/pki/apiserver.crt
/etc/kubernetes/pki/apiserver.key
2、kubelet客户端证书, 用作 kube-apiserver 主动向 kubelet 发起请求时的客户端认证
/etc/kubernetes/pki/apiserver-kubelet-client.crt
/etc/kubernetes/pki/apiserver-kubelet-client.key



三、kube-apiserver 代理根证书(客户端证书)
用在requestheader-client-ca-file配置选项中, kube-apiserver 使用该证书来验证客户端证书是否为自己所签发
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-ca.key

由此根证书签发的证书只有一组:
代理层(如汇聚层aggregator)使用此套代理证书来向 kube-apiserver 请求认证
代理端使用的客户端证书, 用作代用户与 kube-apiserver 认证
/etc/kubernetes/pki/front-proxy-client.crt
/etc/kubernetes/pki/front-proxy-client.key

三、etcd 集群根证书
etcd集群所用到的证书都保存在/etc/kubernetes/pki/etcd这路径下, 很明显, 这一套证书是用来专门给etcd集群服务使用的, 设计以下证书文件
etcd 集群根证书CA(etcd 所用到的所有证书的签发机构)
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/etcd/ca.key

由此根证书签发机构签发的证书有:
1、etcd server 持有的服务端证书
/etc/kubernetes/pki/etcd/server.crt
/etc/kubernetes/pki/etcd/server.key

2、peer 集群中节点互相通信使用的客户端证书
/etc/kubernetes/pki/etcd/peer.crt
/etc/kubernetes/pki/etcd/peer.key
注: Peer:对同一个etcd集群中另外一个Member的称呼
3、pod 中定义 Liveness 探针使用的客户端证书
kubeadm 部署的 Kubernetes 集群是以 pod 的方式运行 etcd 服务的, 在该 pod 的定义中, 配置了 Liveness 探活探针
/etc/kubernetes/pki/etcd/healthcheck-client.crt
/etc/kubernetes/pki/etcd/healthcheck-client.key

当你 describe etcd 的 pod 时, 会看到如下一行配置:
Liveness: exec [/bin/sh -ec ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key get foo] delay=15s timeout=15s period=10s #success=1 #failure=8

4、配置在 kube-apiserver 中用来与 etcd server 做双向认证的客户端证书
/etc/kubernetes/pki/apiserver-etcd-client.crt
/etc/kubernetes/pki/apiserver-etcd-client.key

image-20230425155408675

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
Calico网络模型主要工作组件:
1.Felix:运行在每一台 Host 的 agent 进程,主要负责网络接口管理和监听、路由、ARP 管理、ACL 管理和同步、状态上报等。保证跨主机容器网络互通。
2.etcd:分布式键值存储,相当于k8s集群中的数据库,存储着Calico网络模型中IP地址等相关信息。主要负责网络元数据一致性,确保 Calico 网络状态的准确性;
3.BGP Client(BIRD):Calico 为每一台 Host 部署一个 BGP Client,即每台host上部署一个BIRD。 主要负责把 Felix 写入 Kernel 的路由信息分发到当前 Calico 网络,确保 Workload 间的通信的有效性;
4.BGP Route Reflector:在大型网络规模中,如果仅仅使用 BGP client 形成 mesh 全网互联的方案就会导致规模限制,因为所有节点之间俩俩互联,需要 N^2 个连接,为了解决这个规模问题,可以采用 BGP 的 Router Reflector 的方法,通过一个或者多个 BGP Route Reflector 来完成集中式的路由分发。



扩展:calico的IPIP模式和BGP模式对比分析
1)IPIP
把一个IP数据包又套在一个IP包里,即把IP层封装到IP层的一个 tunnel,它的作用其实基本上就相当于一个基于IP层的网桥,一般来说,普通的网桥是基于mac层的,根本不需要IP,而这个ipip则是通过两端的路由做一个tunnel,把两个本来不通的网络通过点对点连接起来;

calico以ipip模式部署完毕后,node上会有一个tunl0的网卡设备,这是ipip做隧道封装用的,也是一种overlay模式的网络。当我们把节点下线,calico容器都停止后,这个设备依然还在,执行 rmmodipip命令可以将它删除。

2)BGP
BGP模式直接使用物理机作为虚拟路由路(vRouter),不再创建额外的tunnel

边界网关协议(BorderGateway Protocol, BGP)是互联网上一个核心的去中心化的自治路由协议。它通过维护IP路由表或‘前缀’表来实现自治系统(AS)之间的可达性,属于矢量路由协议。BGP不使用传统的内部网关协议(IGP)的指标,而是基于路径、网络策略或规则集来决定路由。因此,它更适合被称为矢量性协议,而不是路由协议,通俗的说就是将接入到机房的多条线路(如电信、联通、移动等)融合为一体,实现多线单IP;

BGP 机房的优点:服务器只需要设置一个IP地址,最佳访问路由是由网络上的骨干路由器根据路由跳数与其它技术指标来确定的,不会占用服务器的任何系统;


常见网络插件
Flannel
Calico
Weave
canal
kubeadm init --config kubeadm.yaml --ignore-preflight-errors=SystemVerification


apiVersion: kubeadm.k8s.io/v1beta2
kind: ClusterConfiguration
kubernetesVersion: v1.20.6
controlPlaneEndpoint: 192.168.13.249:16443
imageRepository: registry.aliyuncs.com/google_containers
apiServer:
certSANs:
- 192.168.13.211
- 192.168.13.177
- 192.168.13.188
- 192.168.13.249
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/16
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
kubectl label node xianchaonode1 node-role.kubernetes.io/worker=worker
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# 脚本一键安装
#!/bin/bash
. /etc/init.d/functions

# IP地址,默认为本机第一块网卡IP地址(不包含lo网卡)
ip=
# 主机名称,默认为当前主机名称
hostName=master
# Docker版本
dockerVersion=20.10.6
# Kubernetes版本
k8sVersion=1.23.0
# Pod网段
podSubnet="10.244.0.0/16"
# Service网段
serviceSubnet="10.10.0.0/16"

openNetwork(){
ping -c 1 www.baidu.com > /dev/null 2>&1

if [ $? -eq 0 ];then
action "外网权限:"
else
action "外网权限:"
echo "此脚本需要访问外网权限才可成功执行,退出脚本"
exit 5
fi
}

stopFirewall(){
systemctl disable firewalld --now &>/dev/null
setenforce 0 &>/dev/null
sed -i.$(date +%F) -r 's/SELINUX=[ep].*/SELINUX=disabled/g' /etc/selinux/config

if (grep SELINUX=disabled /etc/selinux/config) &>/dev/null;then
action "关闭防火墙:"
else
action "关闭防火墙:" false
fi
}

hostName(){
if [[ -z ${ip} ]];then
ip=$(ip addr | grep -oP '(?<=inet\s)\d+\.\d+\.\d+\.\d+'|egrep -v "127.0.0.1|172.17.0.1"|awk NR==1)
fi

if [[ -z ${hostName} ]];then
hostName="${HOSTNAME}"
fi

if ! (egrep -w "${ip} +${hostName}" /etc/hosts) &>/dev/null;then
hostnamectl set-hostname ${hostName}
echo "${ip} ${hostName}" >> /etc/hosts
fi

if (egrep -w "${ip} +${hostName}" /etc/hosts) &>/dev/null;then
action "添加本地域名解析:"
else
action "添加本地域名解析:" false
fi
}

timeSync(){
if ! (which ntpdate &>/dev/null);then
echo -e "\033[32m# ntpdate未安装,开始进行安装....\033[0m"
(yum -y install ntpdate) &>/dev/null;sleep 0.3
if (which ntpdate &>/dev/null);then
action "ntpdate安装成功:"
fi
fi

if (ntpdate ntp1.aliyun.com &>/dev/null);then
if ! (egrep "ntpdate +ntp1.aliyun.com" /var/spool/cron/root &>/dev/null);then
echo "0 1 * * * ntpdate ntp1.aliyun.com" >> /var/spool/cron/root
fi
action "时间同步:"
else
action "时间同步:" false
fi
}

swapOff(){
swapoff --all
sed -i -r '/swap/ s/^/#/' /etc/fstab

if [[ $(free | grep -i swap | awk '{print $2}') -eq 0 ]]; then
action "关闭交换分区:"
else
action "关闭交换分区:" false
fi
}

addKernelArg(){
KernelArg=("net.bridge.bridge-nf-call-ip6tables" "net.bridge.bridge-nf-call-iptables" "net.ipv4.ip_forward")

# 判断内核参数是否存在,如果不存在则添加
for ((i=0;i<${#KernelArg[@]};i++))do
if [[ $(sysctl -n ${KernelArg[i]}) -ne 1 ]];then
echo "${KernelArg[i]} = 1" >> /etc/sysctl.d/kubernetes.conf
fi
done
modprobe br_netfilter &>/dev/null
sysctl -p /etc/sysctl.d/kubernetes.conf &>/dev/null

if [[ $(sysctl -n ${KernelArg[0]}) -eq 1 && $(sysctl -n ${KernelArg[1]}) -eq 1 && $(sysctl -n ${KernelArg[2]}) -eq 1 ]]; then
action "添加内核参数"
else
action "添加内核参数" false
fi

}

ipvs(){
if (command -v ipset &>/dev/null && command -v ipvsadm &>/dev/null);then
cat > /etc/sysconfig/modules/ipvs.modules <<EOF
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF
chmod +x /etc/sysconfig/modules/ipvs.modules
/etc/sysconfig/modules/ipvs.modules
else
echo -e "\033[32m# ipvs未安装,开始进行安装....\033[0m"
yum -y install ipset ipvsadm &>/dev/null
if (command -v ipset &>/dev/null && command -v ipvsadm &>/dev/null);then
action "ipvs安装成功:"
cat > /etc/sysconfig/modules/ipvs.modules <<EOF
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
EOF
chmod +x /etc/sysconfig/modules/ipvs.modules
/etc/sysconfig/modules/ipvs.modules

fi
fi
modprobe br_netfilter &>/dev/null

if (lsmod | grep -q -e ip_vs -e nf_conntrack_ipv4)&>/dev/null; then
action "启用ipvs模块:"
else
action "启用ipvs模块:" false
fi
}
dockerInstall(){
if ! (command -v docker &>/dev/null);then
echo -e "\033[32m# Docker未安装,开始进行安装....\033[0m"
(curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo) &>/dev/null
(wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo) &>/dev/null
(yum install -y yum-utils) &>/dev/null
(yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo) &>/dev/null
(yum install docker-ce-${dockerVersion} docker-ce-cli-${dockerVersion} -y) &>/dev/null
if (command -v docker &>/dev/null);then
action "Docker安装成功:"
else
action "Docker安装成功:" false
fi
fi


mkdir /etc/docker &>/dev/null
if [[ -f /etc/docker/daemon.json ]];then
mv /etc/docker/daemon.json{,.$(date +%F)}
fi

cat <<EOF > /etc/docker/daemon.json
{
"registry-mirrors": ["https://aoewjvel.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
(systemctl enable docker --now) &>/dev/null

if [[ -f /etc/docker/daemon.json ]];then
action "Docker镜像加速源:"
else
action "Docker镜像加速源:"
fi
}

k8sInstall(){
k8scommand=("kubeadm" "kubelet" "kubectl")

if [[ -f /etc/yum.repos.d/kubernetes.repo ]];then
mv /etc/yum.repos.d/kubernetes.repo{,.$(date +%F)}
fi

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

echo -e "\033[32m# 正在安装K8S,请耐心等待......\033[0m"
(yum -y install --setopt=obsoletes=0 kubeadm-${k8sVersion} kubelet-${k8sVersion} kubectl-${k8sVersion}) &>/dev/null
systemctl enable kubelet.service --now &>/dev/null

for ((i=0;i<${#k8scommand[@]};i++))do
if (command -v ${k8scommand[i]} &>/dev/null);then
action "安装${k8scommand[i]}组件:"
else
action "安装${k8scommand[i]}组件:" false
fi
done
}

k8sInit(){
# 通过hosts文件获取IP地址
if [[ -z ${ip} ]];then
ip=$(grep ${HOSTNAME} /etc/hosts|awk '{print $1}'| awk NR==1)
fi

if [[ -f /root/kubeadm-config.yaml ]];then
mv /root/kubeadm-config.yaml{,.$(date +%F)}
fi

cat >> /root/kubeadm-config.yaml << EOF
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: ${ip}
bindPort: 6443
nodeRegistration:
imagePullPolicy: IfNotPresent
name: node
taints: null
---
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controllerManager: {}
dns: {}
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: ${k8sVersion}
networking:
dnsDomain: cluster.local
serviceSubnet: ${serviceSubnet}
podSubnet: ${podSubnet}
scheduler: {}
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF

if [[ -f /root/kubeadm-config.yaml ]];then
action "生成K8s初始化文件:"
else
action "生成K8s初始化文件:" false
fi
echo -e "\033[32m# K8s初始化中,时间可能较长,可以使用 tailf k8s_init.log 可追踪整个过程....\033[0m"
echo
kubeadm init --config /root/kubeadm-config.yaml --ignore-preflight-errors=SystemVerification &>k8s_init.log
if [[ $? -eq 0 ]];then
action "K8s初始化:"
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
else
action "K8s初始化:" false
exit 5
fi
}

k8sNetwork(){
(wget -O /root/calico.yaml http://120.48.34.146/calico.yaml) &>/dev/null
(kubectl apply -f /root/calico.yaml) &>/dev/null

if [[ $? -eq 0 ]];then
action "K8s网络插件:"
else
action "K8s网络插件:" false
fi
}

k8sTaint(){
(kubectl taint nodes --all node-role.kubernetes.io/master-) &>/dev/null

if [[ $? -eq 0 ]];then
action "设置Master节点可调度:"
else
action "设置Master节点可调度:" false
fi
}

initEnv(){
clear;echo "一键部署单机版K8S脚本"
openNetwork
hostName
stopFirewall
swapOff
timeSync
ipvs
addKernelArg
dockerInstall
}

k8s(){
clear;k8sInstall
k8sInit
k8sNetwork
k8sTaint

echo
echo -e "\033[32m# K8s单机版部署完成,等待Pod全部运行成功即可使用 使用 kubectl get pods -n kube-system 关注Pod状态...\033[0m"
bash
}

initEnv
k8s


# 部署 dashboard

1
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml
1
2
3
kubernetes-dashboard   dashboard-metrics-scraper-79c5968bdc-24bws   1/1     Running   0          2m5s
kubernetes-dashboard kubernetes-dashboard-658485d5c7-9hpsc 1/1 Running 0 2m5s

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
设置访问端口
kubectl edit svc kubernetes-dashboard -n kubernetes-dashboard

:/type
改为 NodePort

kubectl get svc -A |grep kubernetes-dashboard
## 找到端口,在安全组放行

[root@master ~]# kubectl get svc -A |grep kubernetes-dashboard
kubernetes-dashboard dashboard-metrics-scraper ClusterIP 10.96.232.147 <none> 8000/TCP 6m7s
kubernetes-dashboard kubernetes-dashboard NodePort 10.96.28.103 <none> 443:31882/TCP 6m7s


# 通过token访问
[root@master ~]# vi dash-user.yml
#创建访问账号,准备一个yaml文件; vi dash.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard

[root@master ~]# kubectl apply -f dash-user.yml
serviceaccount/admin-user created
clusterrolebinding.rbac.authorization.k8s.io/admin-user created

#获取访问令牌
kubectl -n kubernetes-dashboard get secret $(kubectl -n kubernetes-dashboard get sa/admin-user -o jsonpath="{.secrets[0].name}") -o go-template="{{.data.token | base64decode}}"
eyJhbGciOiJSUzI1NiIsImtpZCI6IkY5LU9Jc0pnblNNOVJhM1dlWVl6c09DTjNnbFNQa1hzcWhOSThHVENEdlUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLXp3NmRzIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkOTRmMDZlNi1kNTkxLTRmMzktODMxNC0zNzI2MDVhM2RkN2MiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.TMTc8QDTIo3qhsGTYD05c8txOyq2op7xW9ZQB5ICVw5x4fg-L0iUnn1jGyo4REP6ci63zBR-xmirqHy1eQ5GgT1nwGZnv4oKqNElm8-wkAit5rEgVN7ATo5GN3VrRQgNyBsHwqFhe1SsrK47flCZcmBvWbgmU4ugFfrNZ8xHkSURKrLAPUPjsYUC6ugyrEMqfwhcxzMiZcSKxG20jjLgVK7Pd_T01NAObUb_lCjq7mrEV0KdHcYTtSYwVwV88mTu5vwb39k-10V0s6HOqiDx87lp7fPtctrcR3mRiT1PkEvsyhCb8qAuFDPuaJZtESFu2dCXP_fVmv5g7AhSO_qUHg

image-20230410143529721

如果页面没有接受风险,换浏览器或者空白输入 thisisunsafe

通过 token 访问 dashboard

1
2
3
4
# 查看token 
[root@master1 ~]# kubectl create clusterrolebinding dashboard-cluster-admin --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:kubernetes-dashboard
[root@master1 ~]# kubectl get secrets -n kubernetes-dashboard
[root@master1 ~]# kubectl describe secrets kubernetes-dashboard-token-dmv8x -n kubernetes-dashboard

通过 kubeconfig 访问 dashborad

1
2
3
4
5
6
7
cd /etc/kubernetes/pki/
kubectl config set-cluster kubernetes --certificate-authority=./ca.crt --server="https://192.168.40.249:6443" --embed-certs=true --kueconfig=/root/dashboard-admin.conf
DEF_NS_ADMIN_TOKEN=$(kubectl get secret kubernetes-dashboard-token-dmv8x -n kubernetes-dashboard -o jsonpath={.data.token}|base64 -d
kubectl config set-credentials dashboard-admin --token=$DEF_NS_ADMIN_TOKEN --kubeconfig=/root/dashboard-admin.conf
kubectl config set-context dashboard-admin@kubernetes --cluster=kubernetes --user=dashboard-admin --kubeconfig=/root/dashboard-admin.onf
kubectl config use-context dashboard-admin@kubernetes --kubeconfig=/root/dashboard-admin.conf
vim /root/dashboard-admin.conf

# 安装 containerd

对于 1.24 以上的版本,使用 containerd 管理和运行容器,在此版本之前使用的是 docker

Kubernetes 中的容器运行时就像一个保姆一样,它负责管理容器的生命周期和资源隔离,确保容器能够在节点上稳定地运行。其中,containerd 就是一个常用的容器运行时,它实现了 Kubernetes 规定的 CRI 接口,因此可以被 Kubernetes 作为容器运行时来使用。简单来说,就是 Kubernetes 需要容器运行时来管理和运行容器,而 containerd 就是 Kubernetes 中常用的一种容器运行时,能够让 Kubernetes 更加高效、稳定地运行容器化应用。

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
yum install  containerd.io-1.6.6 -y
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
vim /etc/containerd/config.toml # 修改
SystemdCgroup = true
sandbox_image="registry.aliyuncs.com/google_containers/pause:3.7"



相比 cgroupfs,systemd-cgroup 在资源隔离方面提供了更好的性能和更多的特性。例如,systemd-cgroup 可以使用更多的内存压缩算法,以便更有效地使用内存。此外,systemd-cgroup 还提供了更好的 cgroup 监控和控制机制,可以更精确地调整容器的资源使用量。
总之,使用 systemd-cgroup 作为容器运行时的 cgroup 驱动程序,可以提高 Kubernetes 集群中容器的资源管理效率,从而提升整个集群的性能。

在 Kubernetes 中,每个 Pod 中都有一个 pause 容器,这个容器不会运行任何应用,只是简单地 sleep。它的作用是为了保证 Pod 中所有的容器共享同一个网络命名空间和 IPC 命名空间。pause 容器会在 Pod 的初始化过程中首先启动,然后为 Pod 中的其他容器创建对应的网络和 IPC 命名空间,并且在其他容器启动之前保持运行状态,以保证其他容器可以加入到共享的命名空间中。
简单来说,pause 容器就是一个占位符,它为 Pod 中的其他容器提供了一个共享的环境,使它们可以共享同一个网络和 IPC 命名空间。这也是 Kubernetes 实现容器间通信和网络隔离的重要机制之一。

创建/etc/crictl.yaml文件
[root@xianchaomaster1 ~]#cat > /etc/crictl.yaml <<EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF

这个文件是 crictl 工具的配置文件,用于指定与 containerd 交互的相关设置:

指定unix:///run/containerd/containerd.sock 是为了告诉 crictl 使用 Unix 域套接字的方式来连接 containerd 的 API。containerd 提供了一个 socket 文件 /run/containerd/containerd.sock,crictl 通过连接该 socket 文件,可以与 containerd 进行通信,管理容器和镜像等操作。

1)runtime-endpoint:指定 containerd 的运行时接口地址,以便 crictl 可以与 containerd 通信来管理容器生命周期和资源隔离。
2)image-endpoint:指定 containerd 的镜像接口地址,以便 crictl 可以与 containerd 通信来管理镜像的拉取和推送。
3)timeout:指定 crictl 等待 containerd 响应的最大时间,避免出现无响应的情况。
4)debug:开启或关闭 crictl 的调试模式,方便排查问题。


#systemctl enable containerd --now

配置containerd镜像加速器,k8s所有节点均按照以下配置:
编辑vim /etc/containerd/config.toml文件
找到config_path = "",修改成如下目录:
config_path = "/etc/containerd/certs.d"

#保存退出
mkdir /etc/containerd/certs.d/docker.io/ -p
vim /etc/containerd/certs.d/docker.io/hosts.toml
#写入如下内容:
[host."https://vh3bm52y.mirror.aliyuncs.com",host."https://registry.docker-cn.com"]
capabilities = ["pull","push"]

重启containerd:
systemctl restart containerd


yum install  docker-ce -y
# 为了使用dockerfile所以还要装docker
vim /etc/docker/daemon.json
写入如下内容:
{
 "registry-mirrors":["https://vh3bm52y.mirror.aliyuncs.com","https://registry.docker-cn.com","https://docker.mirrors.ustc.edu.cn","https://dockerhub.azk8s.cn","http://hub-mirror.c.163.com"]
}

重启docker:
systemctl restart docker
1
2
3
1.ctr是containerd自带的CLI命令行工具,crictl是k8s中CRI(容器运行时接口)的客户端,k8s使用该客户端和containerd进行交互;

2.ctr和crictl命令具体区别如下,也可以--help查看。crictl缺少对具体镜像的管理能力,可能是k8s层面镜像管理可以由用户自行控制,能配置pod里面容器的统一镜像仓库,镜像的管理可以有habor

image-20230426170330957

1
2
3
K8s官方文档:https://kubernetes.io/
K8s中文官方文档: https://kubernetes.io/zh/
K8s Github地址:https://github.com/kubernetes/kubernetes

# Namespace

Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。 这些虚拟集群被称为命名空间。

命名空间 namespace 是 k8s 集群级别的资源,可以给不同的用户、租户、环境或项目创建对应的命名空间

命名空间适用于存在很多跨多个团队或项目的用户的场景。默认存放在 default 空间下

隔离资源,不隔离网络

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
[root@master ~]# kubectl get ns 
NAME STATUS AGE
calico-system Active 41h
default Active 41h
kube-node-lease Active 41h
kube-public Active 41h
kube-system Active 41h
kubernetes-dashboard Active 27m
tigera-operator Active 41h

# 查看所有命名空间下
[root@master ~]# kubectl get pods -A

# 默认命名空间
[root@master ~]# kubectl get pods

# 查看指定名称空间
[root@master ~]# kubectl get pods -n kube-system

# 删除名称空间
kubectl delete ns 111

# 创建名称空间
kubectl create ns 111
kubectl get ns

# 通过yaml创建名称空间
apiVersion: v1
kind: Namespace
metadata:
name: hello

kubectl explain ResourceQuota
# 创建资源限额的namespace
apiVersion: v1
kind: ResourceQuota
metadata:
name: mem-cpu-quota
namespace: testing
spec:
hard:
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 4Gi

[root@master1 ~]# kubectl get resourcequotas -n testing
NAME AGE REQUEST LIMIT
mem-cpu-quota 18s requests.cpu: 0/2, requests.memory: 0/1Gi limits.cpu: 0/4, limits.memory: 0/2Gi

request node必须有空闲的request指定的内存才会调度pod到该node上
limit是限制不能超过指定的内存和CPU
# 在namespace下面定义的pod加起来必须满足request和limit

apiVersion: v1
kind: Pod
metadata:
name: pod-test
namespace: test
labels:
app: tomcat-pod-test
spec:
containers:
- name: tomcat-test
ports:
- containerPort: 8080
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent
resources:
requests:
memory: "100Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2"

# 即pod request和limit不能超过namespace的request和limit,否则不能创建
# namespace指定request和limit,pod也必须指定

# pod超过了ns的限制会无法创建

# pod

运行中的一组容器,Pod 是 kubernetes 中应用的最小调度单元 k8s 是通过定义一个 Pod 的资源,然后在 Pod 里面运行容器,容器需要指定一个镜像,这样就可以用来运行具体的服务。

一个 Pod 封装一个容器(也可以封装多个容器),Pod 里的容器共享存储、网络等。也就是说,应该把整个 pod 看作虚拟机,然后每个容器相当于运行在虚拟机的进程。

Pod 是需要调度到 k8s 集群的工作节点来运行的,具体调度到哪个节点,是根据 scheduler 调度器实现的。

image-20230410144807975 image-20230410144917082

image-20230526165706388

image-20230526170239453

# pod 管理多个容器

pod 之间隔离,pod 中可以运行多个容器,pod 中共享资源

image-20230410145136606

Pod 中可以同时运行多个容器。同一个 Pod 中的容器会自动的分配到同一个 node 上。同一个 Pod 中的容器共享资源、网络环境,它们总是被同时调度,在一个 Pod 中同时运行多个容器是一种比较高级的用法,只有当你的容器需要紧密配合协作的时候才考虑用这种模式。

例如,你有一个容器作为 web 服务器运行,需要用到共享的 volume,有另一个 “sidecar” 容器来从远端获取资源更新这些文件。

一些 Pod 有 init 容器和应用容器。 在应用程序容器启动之前,运行初始化容器。比如 es 就需要有 init 容器修改 es 内核参数

# Pod 网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@master1 pki]# kubectl get pod -A -owide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE
default nginx-7f466444dc-jbhpm 1/1 Running 0 77m 10.244.166.134 node1 <none>
kube-system calico-kube-controllers-57c7b58f66-b7pw7 1/1 Running 0 24h 10.244.166.132 node1 <none>
kube-system calico-node-fdzw9 1/1 Running 1 24h 192.168.13.188 master2 <none>
kube-system calico-node-hz96l 1/1 Running 1 24h 192.168.13.199 node1 <none>

Pod是有IP地址的,假如pod不是共享物理机ip,由网络插件(calico、flannel、weave)划分的ip,每个pod都被分配唯一的IP地址

控制节点组件 pod ip和物理机ip一样
如果pod由calico分配,且不共享物理机IP,那么IP唯一,ip地址段在配置时指定

docker 容器间通信,通过--net container参数,指定其和已经存在的某个容器共享一个 Network Namespace。
docker run --name container2 --net=container:none -it --privileged=true centos

Kubernetes中容器共享的方式
在k8s中,启动Pod时,会先启动⼀个pause 的容器,然后将后续的所有容器都 link 到这个pause 的容器,以实现⽹络共享

image-20230526171816395

image-20230427134601678

image-20230426173422564

# pod 存储

创建 Pod 的时候可以指定挂载的存储卷。 POD 中的所有容器都可以访问共享卷,允许这些容器共享数据。 Pod 只要挂载持久化数据卷,Pod 重启之后数据还是会存在的

volume 可以是 nfs,cefs,或者云存储

image-20230426173643049

Pod 中的所用容器会被一致调度、同节点部署,并且在一个 “共享环境” 中运行。

1)所有容器共享一个 IP 地址和端口空间,意味着容器之间可以通过 localhost 高效访问,不能有端口冲突

2)允许容器之间共享存储卷,通过文件系统交互信息

些容器需要紧密联系,需要一起工作。Pod 提供了比容器更高层次的抽象, Pod 中的所有容器使用同一个网络的 namespace,即相同的 IP 地址和 Port 空间。它们可以直接用 localhost 通信。同样的,这些容器可以共享存储,当 K8s 挂载 Volume 到 Pod 上,本质上是将 volume 挂载到 Pod 中的每一个容器里。

通过 pod 可以实现代码自动更新和日志收集

image-20230426175239516

1
生产环境部署了一个go的应用,而且部署了几百个节点,希望这个应用可以定时的同步最新的代码,以便自动升级线上环境。这时,我们不希望改动原来的go应用,可以开发一个Git代码仓库的自动同步服务,然后通过Pod的方式进行编排,并共享代码目录,就可以达到更新java应用代码的效果。

pod 实现日志收集并 aggregate

image-20230426175303038

使用 Pod 的方式,通过简单的编排,既可以保持原有服务逻辑、部署方式不变,又可以增加新的日志收集服务。
而且如果我们对所有服务的日志生成有一个统一的标准,或者仅对日志收集服务稍加修改,就可以将日志收集服务和其他服务进行 Pod 编排,提供统一、标准的日志收集方式。
这里的 “核心业务服务”、“日志收集服务” 分别是一个镜像,运行在隔离的容器环境中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 创建方式
1.kubectl run 命令行创建 (不常用)
2.yaml文件创建


# pod工作方式
1.自主式pod:直接定义一个pod资源
apiVersion: v1
kind: Pod
metadata:
name: tomcat-test
namespace: default
labels:
app: tomcat
spec:
containers:
- name: tomcat-java
ports:
- containerPort: 8080
image: tomcat:latest
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f
[root@master1 ~]# kubectl get pod -A -owide -l app=tomcat
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default tomcat-test 0/1 ContainerCreating 0 66s <none> node1 <none> <none>
pod被删之后不会在重启 kubectl delete pod

2.控制器管理pod
常见的管理Pod的控制器:Replicaset、Deployment、Job、CronJob、Daemonset、Statefulset。
控制器管理的Pod可以确保Pod始终维持在指定的副本数运行。
kind: Deployment、Job...

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test
labels:
app: nginx-deploy
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: my-nginx
image: xianchao/nginx:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80

# pod 创建机制

kubectl 执行的时候先找 kubeconfig 的环境变量,如果没有就找~/.kube/config

kubectl config view 查看不带 key 的 config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@master1 ~]# kubectl config view 
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.13.249:16443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

# 使用kubernetes-admin访问192.168.13.249:16443,找到apiserver

image-20230427135535238

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kubectl apply -f nginx-deploy.yaml->找到config文件,基于config文件指定的用户访问指定的集群,这样就找到了apiserver

第一步:
通过 kubectl 命令向 apiserver 提交创建pod的请求,apiserver接收到pod创建请求后,会将pod的属性信息(metadata)写入etcd。

第二步:
apiserver触发watch机制准备创建pod,信息转发给调度器scheduler,调度器使用调度算法选择node,调度器将node信息给apiserver,apiserver将绑定的node信息写入etcd

第三步:
apiserver又通过watch机制,调用kubelet,指定pod信息,调用容器运行时创建并启动pod内的容器。

第四步:
创建完成之后反馈给kubelet, kubelet又将pod的状态信息给apiserver,
apiserver又将pod的状态信息写入etcd。

# yaml 创建 pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
[root@master2 ~]# kubectl explain pod 
KIND: Pod
VERSION: v1

DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource is
created by clients and scheduled onto hosts.

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

spec <Object>
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

status <Object>
Most recently observed status of the pod. This data may not be up to date.
Populated by the system. Read-only. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

[root@master2 ~]# kubectl explain pod.metadata
KIND: Pod
VERSION: v1

RESOURCE: metadata <Object>

DESCRIPTION:
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

ObjectMeta is metadata that all persisted resources must have, which
includes all objects users must create.

FIELDS:
annotations <map[string]string>
Annotations is an unstructured key value map stored with a resource that
may be set by external tools to store and retrieve arbitrary metadata. They
are not queryable and should be preserved when modifying objects. More
info: http://kubernetes.io/docs/user-guide/annotations

[root@master2 ~]# kubectl explain pod.spec
KIND: Pod
VERSION: v1

RESOURCE: spec <Object>

DESCRIPTION:
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

PodSpec is a description of a pod.

FIELDS:
activeDeadlineSeconds <integer>
Optional duration in seconds the pod may be active on the node relative to
StartTime before the system will actively try to mark it failed and kill
associated containers. Value must be a positive integer.

affinity <Object>
If specified, the pod's scheduling constraints


[root@master2 ~]# kubectl explain pod.spec.containers
KIND: Pod
VERSION: v1

RESOURCE: containers <[]Object>

DESCRIPTION:
List of containers belonging to the pod. Containers cannot currently be
added or removed. There must be at least one container in a Pod. Cannot be
updated.

A single application container that you want to run within a pod.

FIELDS:
args <[]string>
Arguments to the entrypoint. The docker image's CMD is used if this is not
provided. Variable references $(VAR_NAME) are expanded using the
container's environment. If a variable cannot be resolved, the reference in
the input string will be unchanged. The $(VAR_NAME) syntax can be escaped
with a double $$, ie: $$(VAR_NAME). Escaped references will never be
expanded, regardless of whether the variable exists or not. Cannot be
updated. More info:
https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

[root@master2 ~]# kubectl explain pod.spec.containers.ports
KIND: Pod
VERSION: v1

RESOURCE: ports <[]Object>

DESCRIPTION:
List of ports to expose from the container. Exposing a port here gives the
system additional information about the network connections a container
uses, but is primarily informational. Not specifying a port here DOES NOT
prevent that port from being exposed. Any port which is listening on the
default "0.0.0.0" address inside a container will be accessible from the
network. Cannot be updated.

ContainerPort represents a network port in a single container.

FIELDS:
containerPort <integer> -required-
Number of port to expose on the pod's IP address. This must be a valid port
number, 0 < x < 65536.


[root@master1 ~]# vim pod2.yaml
apiVersion: v1
apiVersion: v1
kind: Pod
metadata:
labels:
app: tomcat
name: tomcat-cattt
namespace: default
spec:
activeDeadlineSeconds: 10000
containers: # 对象列表下面字段需要有 -
- name: tomcat-doggg
image: tomcat:9.0
imagePullPolicy: IfNotPresent # always 总是从镜像仓库拉取镜像 ifnot 有就用,没有就拉
ports:
- containerPort: 8080
# metadat下面对齐需要一样,spec下面的对齐需要一样,但是metadata和spec下面的字段对齐不需要一样
[root@master1 ~]# kubectl apply -f pod2.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
# pod 常用命令
[root@master ~]# kubectl run mynginx --image=nginx --image-pull-policy='IfNotPresent' --port=8080

NAMESPACE NAME READY STATUS RESTARTS AGE
default mynginx 1/1 Running 0 84s

[root@master ~]#kubectl describe pod
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m17s default-scheduler Successfully assigned default/mynginx to node2
Normal Pulling 2m16s kubelet Pulling image "nginx"
Normal Pulled 71s kubelet Successfully pulled image "nginx" in 1m5.193735599s
Normal Created 67s kubelet Created container mynginx
Normal Started 67s kubelet Started container mynginx

[root@master ~]# docker ps | grep mynginx
[root@master ~]# kubectl get pods -A
[root@master1 ~]# kubectl get pod -l app=tomcat
[root@master1 ~]# kubectl get pod --show-labels

# 删除pod
[root@master ~]# kubectl delete pod mynginx -n default

# yaml创建pod
apiVersion: v1
kind: Pod
metadata:
labels:
run: mynginx
name: mynginx
# namespace: default
spec:
containers:
- image: nginx
name: mynginx

[root@master ~]# kubectl apply -f pod.yml
[root@master ~]# kubectl delete -f pod.yml


[root@master ~]# kubectl logs -f mynginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/04/10 07:09:42 [notice] 1#1: using the "epoll" event method
2023/04/10 07:09:42 [notice] 1#1: nginx/1.21.5
2023/04/10 07:09:42 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/04/10 07:09:42 [notice] 1#1: OS: Linux 3.10.0-1160.el7.x86_64
2023/04/10 07:09:42 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023/04/10 07:09:42 [notice] 1#1: start worker processes
2023/04/10 07:09:42 [notice] 1#1: start worker process 31
2023/04/10 07:09:42 [notice] 1#1: start worker process 32

# 每个podk8s都会分配ip,保证k8s中所有应用都能访问到
[root@master ~]# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mynginx 1/1 Running 0 3m55s 192.168.104.4 node2 <none> <none>
# 使用IP + Port访问
[root@master ~]# curl 192.168.104.4

# 进入pod
[root@master ~]# kubectl exec -it mynginx -- /bin/bash
# 进入pod指定的容器 -c
[root@master1 ~]# kubectl exec -it tomcat-cattt -c tomcat-doggg -- /bin/bash

集群中的任意一个机器,任意一个应用都能通过pod分配的ip访问pod

# kubectl get pods -l app=tomcat

# 定位pod错误,查看describe和logs

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox 1/1 Terminating 0 127m 10.244.166.130 node1 <none> <none>
10.244网段只能在集群内访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# pod内部署多容器
apiVersion: v1
kind: Pod
metadata:
labels:
run: myapp
name: myapp
spec:
containers:
- image: nginx
name: nginx
- image: tomcat:8.5.68
name: tomcat

# 同一pod内共享网络空间和存储
pod内只需要通过127.0.0.1即可访问

pod内两个容器占用同一个端口会导致error

# labels

标签其实就一对 key/value ,被关联到对象上,比如 Pod, 标签的使用我们倾向于能够表示对象的特殊特点,就是一眼就看出了这个 Pod 是干什么的,标签可以用来划分特定的对象(比如版本,服务类型等),标签可以在创建一个对象的时候直接定义,也可以在后期随时修改,每一个对象可以拥有多个标签,但是,key 值必须是唯一的。创建标签之后也可以方便我们对资源进行分组管理。如果对 pod 打标签,之后就可以使用标签来查看、删除指定的 pod。
在 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
[root@master1 ~]# kubectl label pods tomcat release=v1

[root@master1 ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
tomcat-cattt 1/1 Running 0 69m app=tomcat

[root@master1 ~]# kubectl label pods tomcat-cattt hahaha=shabi
pod/tomcat-cattt labeled

[root@master1 ~]# kubectl get pod --show-labels -n default
NAME READY STATUS RESTARTS AGE LABELS
tomcat-cattt 1/1 Running 0 70m app=tomcat,hahaha=shabi

[root@master1 ~]# kubectl get pod tomcat-cattt --show-labels -n default
NAME READY STATUS RESTARTS AGE LABELS
tomcat-cattt 1/1 Running 0 72m app=tomcat,hahaha=shabi

[root@master1 ~]# kubectl get pod -l hahaha #-l指定标签的key
NAME READY STATUS RESTARTS AGE
tomcat-cattt 1/1 Running 0 72m

[root@master1 ~]# kubectl get pod -l hahaha --show-labels
NAME READY STATUS RESTARTS AGE LABELS
tomcat-cattt 1/1 Running 0 73m app=tomcat,hahaha=shabi

[root@master1 ~]# kubectl get pod -L hahaha # -L显示标签的key-value
NAME READY STATUS RESTARTS AGE HAHAHA
tomcat-cattt 1/1 Running 0 73m shabi

[root@master1 ~]# kubectl get pod -L hahaha,app
NAME READY STATUS RESTARTS AGE HAHAHA APP
tomcat-cattt 1/1 Running 0 74m shabi tomcat
tomcat-test 1/1 Running 0 21h tomcat

[root@master1 ~]# kubectl get pods --all-namespaces --show-labels
NAMESPACE NAME READY STATUS RESTARTS AGE LABELS
default busybox 1/1 Running 0 46h run=busybox
default nginx-7f466444dc-8pbkp 1/1 Running 0 23h k8s-app=nginx,pod-template-hash=7f466444dc
default nginx-7f466444dc-jbhpm 1/1 Running 0 23h k8s-app=nginx,pod-template-hash=7f466444dc
default tomcat-cattt 1/1 Running 0 75m app=tomcat,hahaha=shabi
default tomcat-test 1/1 Running 0 21h app=tomcat
kube-system calico-kube-controllers-57c7b58f66-b7pw7 1/1 Running 0 46h k8s-app=calico-kube

# node 节点选择器

我们在创建 pod 资源的时候,pod 会根据 schduler 进行调度,那么默认会调度到随机的一个工作节点,如果我们想要 pod 调度到指定节点或者调度到一些具有相同特点的 node 节点,
可以使用 pod 中的 nodeName 或者 nodeSelector 字段指定要调度到的 node 节点

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
# 使用nodename调度pod
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: default
labels:
app: myapp
env: dev
spec:
nodeName: master3 # 指定运行pod的node
containers:
- name: tomcat-pod-java
ports:
- containerPort: 8080
image: tomcat:9.0
imagePullPolicy: IfNotPresent
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: # 容器中必须有前台进程才不会启动后立即退出
- "/bin/sh"
- "-c"
- "sleep 3600"

# kubectl get pod -owide
default demo-pod 2/2 Running 0 2m20s 10.244.136.2 master3 <none> <none>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 使用nodeselector调度pod
apiVersion: v1
kind: Pod
metadata:
name: demo-pod-1
namespace: default
labels:
app: myapp
env: dev
spec:
nodeSelector:
disk: ceph
containers:
- name: tomcat-pod-java
ports:
- containerPort: 8080
image: tomcat:9.0
imagePullPolicy: IfNotPresent

# kubectl get pod -owide
demo-pod-1 0/1 Pending 0 17s <none> <none> <none> <none>

[root@master1 ~]# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master1 Ready control-plane,master 47h v1.20.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=
master2 Ready control-plane,master 47h v1.20.6 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master2,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=

# apply -f 不能正常调度,因为所有的node都没有ceph的标签,pod状态为pending

[root@master1 ~]# kubectl label nodes master1 disk=ceph
apply -f
0/4 nodes are available: 1 node(s) didn't match Pod's node affinity, 3 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.
[root@master1 ~]# kubectl label nodes node1 disk=ceph
[root@master1 ~]# kubectl apply -f
demo-pod-1 1/1 Running 0 95s 10.244.166.139 node1 <none> <none>

# 删除节点标签
[root@master1 ~]# kubectl label nodes master1 disk-
node/master1 labeled

# 只有nodename可以强行调度到master,nodeselector不能强行调度到master即使label匹配
# kubectl delete -f x.yaml --force --grace-period=0 强行删除pod
1
2
3
4
5
6
7
8
9
10
11
12
13
# 同时使用nodename 和 nodeselector调度pod
spec:
nodeName: node2
nodeSelector:
disk: ceph
# nodename 和 nodeselector都存在的时候
# 如果其中有一个不匹配
# 会Predicate NodeAffinity failed 亲和性调度失败,因为两个标签不完全匹配

# 但是两个都是匹配时可以正常运行,比如node2中有disk:ceph的标签
# 并且由于nodeName优先级更高,如果master节点同时满足,也能调度到master节点(kubectl v1.23.1)

总结:同一个yaml文件里定义pod资源,如果同时定义了nodeName和NodeSelector,那么条件必须都满足才可以,有一个不满足都会调度失败

# 节点亲和性、反亲和性

node 节点亲和性调度:nodeAffinity

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
[root@master1 ~]# kubectl explain pods.spec.affinity
KIND: Pod
VERSION: v1

RESOURCE: affinity <Object>

DESCRIPTION:
If specified, the pod's scheduling constraints

Affinity is a group of affinity scheduling rules.

FIELDS:
nodeAffinity <Object>
Describes node affinity scheduling rules for the pod.

podAffinity <Object>
Describes pod affinity scheduling rules (e.g. co-locate this pod in the
same node, zone, etc. as some other pod(s)).

podAntiAffinity <Object>
Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod
in the same node, zone, etc. as some other pod(s)).


# node亲和性
[root@prometheus-master ~]# kubectl explain pods.spec.affinity.nodeAffinity
KIND: Pod
VERSION: v1

FIELDS:
preferredDuringSchedulingIgnoredDuringExecution <[]Object>

requiredDuringSchedulingIgnoredDuringExecution <Object>


prefered表示有节点尽量满足这个位置定义的亲和性,这不是一个必须的条件,软亲和性
require表示必须有节点满足这个位置定义的亲和性,这是个硬性条件,硬亲和性

可以使用operator字段来为Kubernetes设置在解释规则时要使用的逻辑操作符。你可以使用In、NotIn, Exists , DoesNotExist,Gt和Lt之一作为操作符。
NotIn和 DoesNotExist可用来实现节点反亲和性行为。你也可以使用节点污点将Pod从特定节点上驱逐。

# 硬亲和性
vim pod6.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity
namespace: default
labels:
app: myapp
spec:
containers:
- name: pod-affinity
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: zone # label key
operator: In # condition
values: # label value
- foo
- bar

由于初始没有node有zone这个label,所以会pending,一定有某个node被label对应的标签,pod会被立刻创建
pod-affinity 0/1 Pending 0 78s <none> prometheus-node2
pod-affinity 0/1 ContainerCreating 0 78s <none> prometheus-node2
pod-affinity 0/1 ContainerCreating 0 79s <none> prometheus-node2
pod-affinity 1/1 Running 0 80s 10.244.64.44 prometheus-node2
由于是硬亲和性,因此如果没有node有对应的label就不调度


[root@master1 ~]# kubectl apply -f pod6.yaml
0/4 nodes are available: 1 node(s) didn't match Pod's node affinity, 3 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.
# 必须在工作node上调度,他无法容忍master的污点。因此即使master有对应的label,pod也不会被调度

# 软亲和性
vim pod7.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-node-affinity-demo-2
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: zone1
operator: In
values:
- foo1
- bar1
weight: 10
- preference:
matchExpressions:
- key: zone2
operator: In
values:
- foo2
- bar2
weight: 20
# 软亲和性,如果没有node匹配label也会调度
# prefer是软亲和性,可以设置weight,由多个preference时,会往weight高的node调度

[root@master1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-node-affinity-demo-2 1/1 Running 0 5s


[root@master1 ~]# kubectl label nodes master1 zone1=foo1
[root@master1 ~]# kubectl label nodes master2 zone2=foo2
# 这里prefer也无法忍受master的污点,所以即使有标签的weight他也不会往master上调度,但是如果多个node都有label,他会往weight高的label调度

# 去除标签
[root@master1 ~]# kubectl label nodes master2 zone2-

# 删除pod
[root@master1 ~]# kubectl delete -f pod7.yaml --force --grace-period=0
[root@master1 ~]# kubectl delete pods pod-affinity --force --grace-period=0

pod 节点亲和性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
pod自身的亲和性调度有两种表示形式:
podaffinity:pod和pod更倾向在一起,把相近的pod结合到相近的位置,如同一区域,同一机架,这样的话pod和pod之间更好通信,比方说有两个机房,这两个机房部署的集群有1000台主机,那么我们希望把nginx和tomcat都部署同一个地方的node节点上,可以提高通信效率;

podunaffinity:pod和pod更倾向不j在一起,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响。

prefer:软亲和性
required:硬亲和性


topologyKey:判断pod是否在同一位置
位置拓扑的键,这个是必须字段
怎么判断是不是同一个位置:
rack=rack1
row=row1
使用rack的键是同一个位置
使用row的键是同一个位置

labelSelector:
我们要判断pod跟别的pod亲和,跟哪个pod亲和,需要靠labelSelector,通过labelSelector选则一组能作为亲和对象的pod资源
namespace:
labelSelector需要选则一组资源,那么这组资源是在哪个名称空间中呢,通过namespace指定,如果不指定namespaces,那么就是当前创建pod的名称空间

vim pod8.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-first
labels:
app2: myapp2
tier: frontend
spec:
containers:
- name: myapp
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent

vim pod9.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: # 根具有那个标签的pod调度
matchExpressions:
- {key: app2, operator: In, values: ["myapp2"]}
topologyKey: kubernetes.io/hostname # kubernetes.io/hostname相同的pod是一个位置

pod-first 1/1 Running 0 19m 10.244.166.145 node1 <none> <none>
pod-second 1/1 Running 0 101s 10.244.166.146 node1 <none> <none>
先判断标签是否一样,来决定pod是否调度到一起,在根据topologyKey决定调度到那个node

# 如果硬亲和label不满足,会pending状态

apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 10
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchExpressions:
- {key: app1, operator: In, values: ["myapp1"]}
# 此时pod-second不满足软亲和性,依然可以调度,但是不会和pod-first调度到一起

pod 反亲和性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
vim pod10.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-first
labels:
app1: myapp1
tier: frontend
spec:
containers:
- name: myapp
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent

[root@master1 ~]# kubectl get pod -owide -l app1=myapp1
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-first 1/1 Running 0 28s 10.244.166.147 node1 <none> <none>

vim pod11.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app1, operator: In, values: ["myapp1"]}
topologyKey: kubernetes.io/hostname

# 只有一个work node的情况下:
pod-first 1/1 Running 0 4m26s 10.244.166.147 node1 <none> <none>
pod-second 0/1 Pending 0 25s <none> <none> <none> <none>
# 说明pod反亲和性也不能容忍master的污点,因为此时只有一个node,于是他pending了

# 如果存在多个工作节点,second会去找topologyKey的value值不同的节点
pod-first 1/1 Running 0 52s 10.244.216.124 prometheus-node1 <none> <none>
pod-second 1/1 Running 0 10s 10.244.64.48 prometheus-node2 <none> <none>


# 更换topologyKey
kubectl label node node1 zone=foo
kubectl label node node2 zone=foo

vim po12.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-first
labels:
app3: myapp3
tier: frontend
spec:
containers:
- name: myapp
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent


vim pod13.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app3 ,operator: In, values: ["myapp3"]}
topologyKey: zone

pod-first 1/1 Running 0 62s 10.244.216.125 prometheus-node1 <none> <none>
pod-second 0/1 Pending 0 5s <none> <none> <none> <none>

# topologyKey 相同时pod会认为这是同一个位置。因此pod-first在node1或node2上时,pod-second不会再这两个节点调度,他又无法忍受master污点,所以他pending了
# 如果在反亲和性这个位置把required改成preferred,那么也会运行。

apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 10
podAffinityTerm:
topologyKey: zone
labelSelector:
matchExpressions:
- {key: app3, operator: In, values: ["myapp3"]}
pod-first 1/1 Running 0 6m43s 10.244.216.125 prometheus-node1 <none> <none>
pod-second 1/1 Running 0 21s 10.244.216.126 prometheus-node1 <none> <none>

podaffinity:pod 节点亲和性,pod 倾向于哪个 pod
podantiaffinity:pod 反亲和性
nodeaffinity:node 节点亲和性,pod 倾向于哪个 node

# 污点,容忍度

给了节点选则的主动权,我们给节点打一个污点,不容忍的 pod 就运行不上来,污点就是定义在节点上的键值属性数据,可以定决定拒绝那些 pod;
taints 是键值数据,用在节点上,定义污点;

tolerations 是键值数据,用在 pod 上,定义容忍度,能容忍哪些污点

pod 亲和性是 pod 属性;但是污点是 node 的属性,污点定义在 k8s 集群的节点上的一个字段

master 上会有污点
Taints: node-role.kubernetes.io/master:NoSchedule

taints 的 effect 用来定义对 pod 对象的排斥等级(效果):

NoSchedule:
仅影响 pod 调度过程,当 pod 能容忍这个节点污点,就可以调度到当前节点,后来这个节点的污点改了,加了一个新的污点,使得之前调度的 pod 不能容忍了,那这个 pod 会怎么处理,对现存的 pod 对象不产生影响

NoExecute:
既影响调度过程,又影响现存的 pod 对象,如果现存的 pod 不能容忍节点后来加的污点,这个 pod 就会被驱逐

PreferNoSchedule:
最好不,也可以,是 NoSchedule 的柔性版本

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
[root@master1 ~]# kubectl get pod -n kube-system -owide | grep master1
calico-node-mptp2 1/1 Running 1 5d4h 192.168.13.177 master1 <none> <none>
coredns-7f89b7bc75-gtmmh 1/1 Running 1 5d4h 10.244.137.67 master1 <none> <none>
coredns-7f89b7bc75-hc7ns 1/1 Running 1 5d4h 10.244.137.68 master1 <none> <none>
etcd-master1 1/1 Running 3 5d4h 192.168.13.177 master1 <none> <none>
kube-apiserver-master1 1/1 Running 5 5d4h 192.168.13.177 master1 <none> <none>
kube-controller-manager-master1 1/1 Running 2 5d4h 192.168.13.177 master1 <none> <none>
kube-proxy-t9td4 1/1 Running 1 5d4h 192.168.13.177 master1 <none> <none>
kube-scheduler-master1 1/1 Running 2 5d4h 192.168.13.177 master1 <none> <none>

# 由于定义了Tolerations,上面这些pod能容忍master的污点
[root@master1 ~]# kubectl describe pods -n kube-system kube-apiserver-master1
Tolerations: :NoExecute op=Exists

# exist NoExecute的trait等级要比NoSchedule高,能容忍NoExecute一定能容忍NoSchedule和PreferNoSchedule
# op=exist 情况下 排斥等级可以理解为 NoExecute > NoSchedule > PreferNoSchedule
# equal是严格比配,不能有exist跨级匹配

[root@master1 ~]# kubectl taint node node1 node-type=prod:NoSchedule
node/node1 tainted


vim pod14.yaml
apiVersion: v1
kind: Pod
metadata:
name: taint-pod
namespace: default
labels:
tomcat: tomcat-pod
spec:
containers:
- name: taint-pod
ports:
- containerPort: 8080
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent

taint-pod 1/1 Running 0 3s 10.244.216.127 prometheus-node1 <none> <none>
重新创建pod,pod无法忍受node1的污点
taint-pod 1/1 Running 0 4s 10.244.64.49 prometheus-node2 <none> <none>


# NoExecute污点,没有定义容忍度的pod会被驱逐
[root@master1 ~]# kubectl taint node node1 node-type=dev:NoExecute
default nginx-7f466444dc-klcl9 1/1 Terminating 0 4m32s 10.244.166.152 node1 <none> <none>

vim pod15.yaml
# 定义容忍度为NoExecute
apiVersion: v1
kind: Pod
metadata:
name: myapp-deploy
namespace: default
labels:
app: myapp
release: canary
spec:
containers:
- name: myapp
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
tolerations:
- key: "node-type"
operator: "Equal"
value: "dev"
effect: "NoExecute"
tolerationSeconds: 3600
# 此时等值匹配情况下,只有能忍受node-type=dev:NoExecute 的node可以被调度
myapp-deploy 1/1 Running 0 2m27s 10.244.166.154 node1 <none> <none>


# 对于equals等值匹配,能容忍noexecute也不能容忍noschedule,因为是严格等值匹配
# 保持pod容忍不变,改变node污点为noschedule
[root@master1 ~]# kubectl taint node node1 node-type=dev:NoSchedule
myapp-deploy 0/1 Pending 0 3s


# 定义容忍度为NoSchedule
apiVersion: v1
kind: Pod
metadata:
name: myapp-deploy
namespace: default
labels:
app: myapp
release: canary
spec:
containers:
- name: myapp
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
tolerations:
- key: "node-type"
operator: "Equal"
value: "dev"
effect: "NoSchedule"
myapp-deploy 1/1 Running 0 28s 10.244.166.157 node1 <none> <none>

# 对于noschedule,不需要定义容忍时间(tolerationSeconds),因为不会驱逐原有pod
# noexecute如果有新的污点pod不能忍受,经过tolerationSeconds pod会被驱逐

# 以下为exists特性:
tolerations:
- key: "node-type"
operator: "Exists"
value: ""
effect: "NoSchedule"
# 可以不写value,只要key和effect匹配就行

tolerations:
- key: "node-type"
operator: "Exists"
value: ""
effect: ""
# 可以容忍key匹配的一切污点
myapp-deploy 1/1 Running 0 6s 10.244.166.158 node1 <none> <none>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:

node.kubernetes.io/not-ready:节点未准备好。这相当于节点状况 Ready 的值为 "False"。
node.kubernetes.io/unreachable:节点控制器访问不到节点. 这相当于节点状况 Ready 的值为 "Unknown"。
node.kubernetes.io/memory-pressure:节点存在内存压力。
node.kubernetes.io/disk-pressure:节点存在磁盘压力。
node.kubernetes.io/pid-pressure: 节点的 PID 压力。
node.kubernetes.io/network-unavailable:节点网络不可用。
node.kubernetes.io/unschedulable: 节点不可调度。
node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 启动时指定了一个“外部”云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
在节点被驱逐时,节点控制器或者 kubelet 会添加带有 NoExecute 效果的相关污点。 如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。

在某些情况下,当节点不可达时,API 服务器无法与节点上的 kubelet 进行通信。 在与 API 服务器的通信被重新建立之前,删除 Pod 的决定无法传递到 kubelet。 同时,被调度进行删除的那些 Pod 可能会继续运行在分区后的节点上。

这些值都可以修改,比如在内存剩余不足指定数量后会自动打上node.kubernetes.io/disk-pressure的污点,且不能手工删除,除非空出指定大小的内存之后

# pod 常见状态和重启策略

image-20230430215552262

第一阶段:
挂起( Pending ):
1、正在创建 Pod 但是 Pod 中的容器还没有全部被创建完成,处于此状态的 Pod 应该检查 Pod 依赖的存储是否有权限挂载、镜像是否可以下载、调度是否正常等

2、我们在请求创建 pod 时,条件不满足,调度没有完成,没有任何一个节点能满足调度条件,已经创建了 pod 但是没有适合它运行的节点叫做挂起,调度没有完成。

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

未知( Unknown ):未知状态,所谓 pod 是什么状态是 apiserver 和运行在 pod 节点的 kubelet 进行通信获取状态信息的,如果节点之上的 kubelet 本身出故障,那么 apiserver 就连不上 kubelet,得不到信息了,就会看 Unknown,通常是由于与 pod 所在的 node 节点通信错误。

Error 状态:Pod 启动过程中发生了错误

成功( Succeeded ):Pod 中的所有容器都被成功终止,即 pod 里所有的 containers 均已 terminated。

第二阶段:
Unschedulable :Pod 不能被调度, scheduler 没有匹配到合适的 node 节点

PodScheduled :pod 正处于调度中,在 scheduler 刚开始调度的时候,还没有将 pod 分配到指定的 node,在筛选出合适的节点后就会更新 etcd 数据,将 pod 分配到指定的 node

Initialized :所有 pod 中的初始化容器已经完成了

ImagePullBackOff :Pod 所在的 node 节点下载镜像失败

Running :Pod 内部的容器已经被创建并且启动。

Ready

扩展:还有其他状态,如下:

Evicted 状态:出现这种情况,多见于系统内存或硬盘资源不足,可 df-h 查看 docker 存储所在目录的资源使用情况,如果百分比大于 85%,就要及时清理下资源,尤其是一些大文件、docker 镜像。

CrashLoopBackOff:容器曾经启动了,但可能又异常退出了

查看内存,CPU,磁盘:

free -mh

top

df -hl

kubectl describe pod_name

kubectl logs pod_name

Pod 重启策略

Pod 的重启策略(RestartPolicy)应用于 Pod 内的所有容器,当某个容器异常退出或者健康检查失败时,kubelet 将根据 重启策略来进行相应的操作。

Pod 的 spec 中包含一个 restartPolicy 字段,其可能取值包括 Always、OnFailure 和 Never。默认值是 Always。

Always:只要容器异常退出,kubelet 就会自动重启该容器。(这个是默认的重启策略)

OnFailure:当容器终止运行且退出码不为 0 时,由 kubelet 自动重启该容器。

Never:不论容器运行状态如何,kubelet 都不会重启该容器。

(重启的不是 pod,是 pod 中的容器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: default
labels:
app: myapp
spec:
restartPolicy: Always
containers:
- name: tomcat-pod-java
ports:
- containerPort: 8080
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent
1
2
3
4
5
6
7
8
9
10
kubectl exec -it demo-pod -- /bin/bash
/usr/local/tomcat/bin/shutdown.sh
# always - complete - 重启
# onFailure - complete - 不会重启
# never - complete状态,不会重启

kill 1
# always error - 立刻重启
# onFailure - error - 重启
# never - error - 不重启

# pod 生命周期

pod 从开始创建到终止退出的时间范围称为 Pod 生命周期

生命周期包含以下几个重要流程:
创建主容器(containers)是必须的操作,初始化容器(initContainers),容器启动后钩子,启动探测、存活性探测,就绪性探测,容器停止前钩子。

其中 initcontainers、容器启动后钩子、探测、容器终止前钩子这些是可选项 -

image-20230430224431424

pod 在整个生命周期的过程中总会处于以下几个状态:

Pending:创建了 pod 资源并存入 etcd 中,但尚未完成调度。

ContainerCreating:Pod 的调度完成,被分配到指定 Node 上。处于容器创建的过程中。通常是在拉取镜像的过程中。

Running:Pod 包含的所有容器都已经成功创建,并且成功运行起来。

Succeeded:Pod 中的所有容器都已经成功终止并且不会被重启(重启策略是 never)

Failed:所有容器都已经终止,但至少有一个容器终止失败,也就是说容器返回了非 0 值的退出状态或已经被系统终止。

Unknown:因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。

pod 生命周期的重要行为:

1、在启动任何容器之前,先创建 pause 基础容器,它初始化 Pod 的环境并为后续加⼊的容器提供共享的名称空间。

2. 初始化容器(initcontainer):

一个 pod 可以拥有任意数量的 init 容器。init 容器是按照顺序以此执行的,并且仅当最后一个 init 容器执行完毕才会去启动主容器。

3. 生命周期钩子:

pod 允许定义两种类型的生命周期钩子,启动后 (post-start) 钩子和停止前 (pre-stop) 钩子

这些生命周期钩子是基于每个容器来指定的,和 init 容器不同的是,init 容器是应用到整个 pod。而这些钩子是针对容器的,是在容器启动后和停止前执行的。比如启动时复制文件到容器中,结束后发送信息到监控

4. 容器探测:

对 Pod 健康状态诊断。分为三种: Startupprobe、Livenessprobe (存活性探测), Readinessprobe (就绪性检测)

Startup(启动探测): 探测容器是否正常运行

Liveness (存活性探测):判断容器是否处于 runnning 状态,根据重启策略决定是否重启容器

Readiness (就绪性检测):判断容器是否准备就绪并对外提供服务,将容器设置为不可用,不接受 service 转发的请求

三种探针用于 Pod 检测:

ExecAction:在容器中执行一个命令,并根据返回的状态码进行诊断,只有返回 0 为成功

TCPSocketAction:通过与容器的某 TCP 端口尝试建立连接

HTTPGetAction:通过向容器 IP 地址的某指定端口的 path 发起 HTTP GET 请求。

image-20230430230843299

6.pod 的终止过程

终止过程主要分为如下几个步骤:

(1) 用户发出删除 pod 命令:kubectl delete pods ,kubectl delete -f yaml

(2) Pod 对象随着时间的推移更新,在宽限期(默认情况下 30 秒),pod 被视为 “dead” 状态

(3) 将 pod 标记为 “Terminating” 状态

(4) 第三步同时运行,监控到 pod 对象为 “Terminating” 状态的同时启动 pod 关闭过程

(5) 第三步同时进行,endpoints 控制器监控到 pod 对象关闭,将 pod 与 service 匹配的 endpoints 列表中删除

(6) 如果 pod 中定义了 preStop 钩子处理程序,则 pod 被标记为 “Terminating” 状态时以同步的方式启动执行;若宽限期结束后,preStop 仍未执行结束,第二步会重新执行并额外获得一个 2 秒的小宽限期

(7) Pod 内对象的容器收到 TERM 信号

(8) 宽限期结束之后,若存在任何一个运行的进程,pod 会收到 SIGKILL 信号

(9) Kubelet 请求 API Server 将此 Pod 资源宽限期设置为 0 从而完成删除操作

# initcontainer

spec 字段下有个 initContainers 字段 (初始化容器),Init 容器就是做初始化工作的容器。可以有一个或多个,如果多个按照定义的顺序依次执行,先执行初始化容器 1,再执行初始化容器 2 等,等初始化容器执行完具体操作之后初始化容器就退出了,只有所有的初始化容器执行完后,主容器才启动。

由于一个 Pod 里容器存储卷是共享的,所以 Init Container 里产生的数据可以被主容器使用到,Init Container 可以在多种 K8S 资源里被使用到,如 Deployment、DaemonSet, StatefulSet、Job 等,但都是在 Pod 启动时,在主容器启动前执行,做初始化工作。

初始化容器与主容器区别是:
1、初始化容器不支持 Readinessprobe, 因为它们必须在 Pod 就绪之前运行完成
2、每个 Init 容器必须运行成功,下一个才能够运行

初始化容器的官方地址:
https://kubernetes.io/docs/concepts/workloads/pods/init-containers/#init-containers-in-use

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
initContainers:
- name: init-myservice
image: busybox:1.28
imagePullPolicy: IfNotPresent
command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
- name: init-mydb
image: busybox:1.28
imagePullPolicy: IfNotPresent
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']

myapp-pod 0/1 Init:0/2 0 4s
由于两个初始化容器一直无法解析nslookup的域名,导致主容器一致不会创建

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
# 初始化容器拉取百度页面,主容器使用
apiVersion: v1
kind: Pod
metadata:
name: initnginx
spec:
initContainers:
- name: install
image: docker.io/library/busybox:1.28
imagePullPolicy: IfNotPresent
command:
- wget
- "-O"
- "/work-dir/index.html"
- "https://www.baidu.com"
volumeMounts:
- name: workdir
mountPath: /work-dir
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}

[root@master1 ~]# curl 10.244.166.161
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title>

# 主容器

1、容器钩子

初始化容器启动之后,开始启动主容器,在主容器启动之后有一个 post start hook(容器启动后钩子)和 pre stop hook(容器结束前钩子),无论启动后还是结束前所做的事我们可以把它放两个钩子,这个钩子就表示用户可以用它来钩住一些命令,非必须选项

postStart:该钩子在容器被创建后立刻执行,如果该钩子对应的探测执行失败,则该容器会被杀死,并根据该容器的重启策略决定是否要重启该容器,这个钩子不需要传递任何参数。

preStop:该钩子在容器被删除前执行,主要用于释放资源和优雅关闭程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
containers:
- image: sample:v2
name: war
lifecycle:
postStart:
exec:
command:
- "cp"
- "/sample.war"
- "/app"
prestop:
httpGet:
host: monitor.com
path: /waring
port: 8080
scheme: HTTP

以上示例中,定义了一个 Pod,包含一个 JAVA 的 web 应用容器,其中设置了 PostStart 和 PreStop 回调函数。即在容器创建成功后,复制 /sample.war 到 /app 文件夹中。而在容器终止之前,发送 HTTP 请求到 http://monitor.com:8080/waring,即向监控系统发送警告。

优雅的删除资源对象

当用户请求删除含有 pod 的资源对象时(如 RC、deployment 等),K8S 为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件),K8S 提供两种信息通知:

1)、默认:K8S 通知 node 执行 docker stop 命令,docker 会先向容器中 PID 为 1 的进程发送系统信号 SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送 SIGKILL 的系统信号强行 kill 掉进程。

2)、使用 pod 生命周期(利用 PreStop 回调函数),它执行在发送终止信号之前。
默认情况下,所有的删除操作的优雅退出时间都在 30 秒以内。kubectl delete 命令支持–grace-period = 的选项,以运行用户来修改默认值。0 表示删除立即执行,并且立即从 API 中删除 pod。在节点上,被设置了立即结束的的 pod,仍然会给一个很短的优雅退出时间段,才会开始被强制杀死。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
spec:
containers:
- name: nginx-demo
image: centos:nginx
lifecycle:
preStop:
exec:
# nginx -s quit gracefully terminate while SIGTERM triggers a quick exit
command: ["/usr/local/nginx/sbin/nginx","-s","quit"]
ports:
- name: http
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
29
30
apiVersion: v1
kind: Pod
metadata:
name: life-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
imagePullPolicy: IfNotPresent
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c","echo 'lifecycle hookshandler' > /usr/share/nginx/html/test.html"]
preStop:
exec:
command:
- "/bin/sh"
- "-c"
- "nginx -s stop"

[root@prometheus-master pod]# kubectl exec -it life-demo -- /bin/bash
root@life-demo:/# ls /usr/share/nginx/html/
50x.html index.html test.html
root@life-demo:/# cat /usr/share/nginx/html/test.html
lifecycle hookshandler

kubectl get event
0s Normal Killing pod/life-demo Stopping container lifecycle-demo-container
0s Normal Killing pod/life-demo Stopping container lifecycle-demo-container

pod 在整个生命周期中有非常多的用户行为:

1、初始化容器完成初始化

2、主容器启动后可以做启动后钩子

3、主容器结束前可以做结束前钩子

4、在主容器运行中可以做一些健康检测,如 startupprobe、livenessprobe,readnessprobe

# 容器探测

在 Kubernetes 中 Pod 是最小的计算单元,而一个 Pod 又由多个容器组成,相当于每个容器就是一个应用,应用在运行期间,可能因为某些意外情况致使程序挂掉。那么如何监控这些容器状态稳定性,保证服务在运行期间不会发生问题,发生问题后进行重启等机制,就成为了重中之重的事情,考虑到这点 kubernetes 推出了活性探针机制。有了存活性探针能保证程序在运行中如果挂掉能够自动重启,但是还有个经常遇到的问题,比如说,在 Kubernetes 中启动 Pod,显示明明 Pod 已经启动成功,且能访问里面的端口,但是却返回错误信息。还有就是在执行滚动更新时候,总会出现一段时间,Pod 对外提供网络访问,但是访问却发生 404,这两个原因,都是因为 Pod 已经成功启动,但是 Pod 的的容器中应用程序还在启动中导致,考虑到这点 Kubernetes 推出了就绪性探针机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: check
namespace: default
labels:
app: check
spec:
containers:
- name: check
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- sleep 10;exit

k8s 提供了三种来实现容器探测的方法,分别是:

1、startupProbe:探测容器中的应用是否已经启动。如果提供了启动探测 (startup probe),则暂时禁用所有其他探测,直到它成功为止。如果启动探测失败,kubelet 将杀死容器,容器服从其重启策略进行重启。如果容器没有提供启动探测,则默认状态为成功 Success。

2、livenessprobe:用指定的方式(exec、tcp、http)检测 pod 中的容器是否正常运行,如果检测失败,则认为容器不健康,那么 Kubelet 将根据 Pod 中设置的 restartPolicy 策略来判断 Pod 是否要进行重启操作,如果容器配置中没有配置 livenessProbe,Kubelet 将认为存活探针探测一直为 success(成功)状态。

3、readnessprobe:就绪性探针,用于检测容器中的应用是否可以接受请求,当探测成功后才使 Pod 对外提供网络访问,将容器标记为就绪状态,可以加到 pod 前端负载,如果探测失败,则将容器标记为未就绪状态,会把 pod 从前端负载移除。

可以自定义在 pod 启动时是否执行这些检测,如果不设置,则检测结果均默认为通过,如果设置,则顺序为 startupProbe>readinessProbe 和 livenessProbe,readinessProbe 和 livenessProbe 是并发关系

目前 LivenessProbe 和 ReadinessProbe、startupprobe 探测都支持下面三种探针:

1、exec:在容器中执行指定的命令,如果执行成功,退出码为 0 则探测成功。

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

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

探针探测结果有以下值:

1、Success:表示通过检测。

2、Failure:表示未通过检测。

3、Unknown:表示检测没有正常进行。

Pod 探针相关的属性:

探针 (Probe) 有许多可选字段,可以用来更加精确的控制 Liveness 和 Readiness 两种探针的行为

initialDelaySeconds:容器启动后要等待多少秒后探针开始工作,单位 “秒”,默认是 0 秒,最小值是 0

periodSeconds: 执行探测的时间间隔(单位是秒),默认为 10s,单位 “秒”,最小值是 1

timeoutSeconds: 探针执行检测请求后,等待响应的超时时间,默认为 1,单位 “秒”。

successThreshold:连续探测几次成功,才认为探测成功,默认为 1,在 Liveness 探针中必须为 1,最小值为 1。

failureThreshold: 探测失败的重试次数,重试一定次数后将认为失败,在 readiness 探针中,Pod 会被标记为未就绪,默认为 3,最小值为 1

两种探针区别:

ReadinessProbe 和 livenessProbe 可以使用相同探测方式,只是对 Pod 的处置方式不同:

readinessProbe 当检测失败后,将 Pod 的 IP:Port 从对应的 EndPoint 列表中删除。

livenessProbe 当检测失败后,将杀死容器并根据 Pod 的重启策略来决定作出对应的措施。

# 启动探测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 1、exec模式
# kubectl explain pod.spec.containers.startupProbe
# startupProbe

apiVersion: v1
kind: Pod
metadata:
name: startupprobe
spec:
containers:
- name: startup
image: tomcat-8.5-jre8
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
startupProbe:
exec:
command:
- "/bin/sh"
- "-c"
- "ps aux | grep tomcat"
initialDelaySeconds: 20 #容器启动后多久开始探测
periodSeconds: 20 #执行探测的时间间隔
timeoutSeconds: 10 #探针执行检测请求后,等待响应的超时时间
successThreshold: 1 #成功多少次才算成功
failureThreshold: 3 #失败多少次才算失败
# 第一次探测失败多久会重启
initialDelaySeconds + (periodSeconds + timeoutSeconds) * failureThreshold

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
# tcpsocket模式
apiVersion: v1
kind: Pod
metadata:
name: startupprobe
spec:
containers:
- name: startup
image: tomcat:9.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
startupProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 20 #容器启动后多久开始探测
periodSeconds: 20 #执行探测的时间间隔
timeoutSeconds: 10 #探针执行检测请求后,等待响应的超时时间
successThreshold: 1 #成功多少次才算成功
failureThreshold: 3 #失败多少次才算失败

startupprobe 0/1 ContainerCreating 0 0s <none> node1 <none> <none>
startupprobe 0/1 ContainerCreating 0 1s <none> node1 <none> <none>
startupprobe 0/1 Running 0 2s 10.244.166.168 node1 <none> <none>
startupprobe 0/1 Running 0 36s 10.244.166.168 node1 <none> <none>
startupprobe 1/1 Running 0 78s 10.244.166.168 node1 <none> <none>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# httpget模式

apiVersion: v1
kind: Pod
metadata:
name: startupprobe
spec:
containers:
- name: startup
image: tomcat:9.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
startupProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 20 #容器启动后多久开始探测
periodSeconds: 20 #执行探测的时间间隔
timeoutSeconds: 10 #探针执行检测请求后,等待响应的超时时间
successThreshold: 1 #成功多少次才算成功
failureThreshold: 3 #失败多少次才算失败

# 存活探测

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
# livenessProbe
# exec

apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
labels:
app: liveness
spec:
containers:
- name: liveness
image: busybox:1.28
imagePullPolicy: IfNotPresent
args: #创建测试探针探测的文件
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
initialDelaySeconds: 10 #延迟检测时间
periodSeconds: 5 #检测时间间隔
exec:
command:
- cat
- /tmp/healthy

liveness-exec 1/1 Running 0 2s 10.244.166.171 node1 <none> <none>
liveness-exec 1/1 Running 1 76s 10.244.166.171 node1 <none> <none>
liveness-exec 1/1 Running 2 2m30s 10.244.166.171 node1 <none> <none>
10 + 20 * 3

容器启动设置执行的命令:

/bin/sh -c “touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600”

容器在初始化后,首先创建一个 /tmp/healthy 文件,然后执行睡眠命令,睡眠 30 秒,到时间后执行删除 /tmp/healthy 文件命令。而设置的存活探针检检测方式为执行 shell 命令,用 cat 命令输出 healthy 文件的内容,如果能成功执行这条命令,存活探针就认为探测成功,否则探测失败。在前 30 秒内,由于文件存在,所以存活探针探测时执行 cat /tmp/healthy 命令成功执行。30 秒后 healthy 文件被删除,所以执行命令失败,Kubernetes 会根据 Pod 设置的重启策略来判断,是否重启 Pod。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# http
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
labels:
test: liveness
spec:
containers:
- name: liveness
image: mydlqclub/springboot-helloworld:0.0.1
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 20 #延迟加载时间
periodSeconds: 5 #重试时间间隔
timeoutSeconds: 10 #超时时间设置
httpGet:
scheme: HTTP
port: 8081
path: /actuator/health
# localhost:8081/actuator/health

上面 Pod 中启动的容器是一个 SpringBoot 应用,其中引用了 Actuator 组件,提供了 /actuator/health 健康检查地址,存活探针可以使用 HTTPGet 方式向服务发起请求,请求 8081 端口的 /actuator/health 路径来进行存活判断:

任何大于或等于 200 且小于 400 的代码表示探测成功。

任何其他代码表示失败。

如果探测失败,则会杀死 Pod 进行重启操作。

httpGet 探测方式有如下可选的控制字段:

scheme: 用于连接 host 的协议,默认为 HTTP。

host:要连接的主机名,默认为 Pod IP,可以在 http request head 中设置 host 头部。

port:容器上要访问端口号或名称。

path:http 服务器上的访问 URI。

httpHeaders:自定义 HTTP 请求 headers,HTTP 允许重复 headers。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# tcp
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcp
labels:
app: liveness
spec:
containers:
- name: liveness
image: nginx
imagePullPolicy: IfNotPresent
livenessProbe:
initialDelaySeconds: 15
periodSeconds: 20
tcpSocket:
port: 80

# 就绪探测

Pod 的 ReadinessProbe 探针使用方式和 LivenessProbe 探针探测方法一样,也是支持三种,只是一个是用于探测应用的存活,一个是判断是否对外提供流量的条件。这里用一个 Springboot 项目,设置 ReadinessProbe 探测 SpringBoot 项目的 8081 端口下的 /actuator/health 接口,如果探测成功则代表内部程序以及启动,就开放对外提供接口访问,否则内部应用没有成功启动,暂不对外提供访问,直到就绪探针探测成功。

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
# readiness 
# exec
# http
# exec
当检测失败后,将 Pod 的 IP:Port 从对应的 EndPoint 列表中删除。
检测成功后才会加到service的endpoint中

apiVersion: v1
kind: Service
metadata:
name: springboot
labels:
app: springboot
spec:
type: NodePort
ports:
- name: server
port: 8080
targetPort: 8080
nodePort: 31180
- name: management
port: 8081
targetPort: 8081
nodePort: 31181
selector:
app: springboot
---
apiVersion: v1
kind: Pod
metadata:
name: springboot
labels:
app: springboot
spec:
containers:
- name: springboot
image: mydlqclub/springboot-helloworld:0.0.1
imagePullPolicy: IfNotPresent
ports:
- name: server
containerPort: 8080
- name: management
containerPort: 8081
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 8081
path: /actuator/health

# 混合使用三种探测

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
# 混合使用三种探测
apiVersion: v1
kind: Service
metadata:
name: springboot-live
labels:
app: springboot
spec:
type: NodePort
ports:
- name: server
port: 8080
targetPort: 8080
nodePort: 31180
- name: management
port: 8081
targetPort: 8081
nodePort: 31181
selector:
app: springboot
---
apiVersion: v1
kind: Pod
metadata:
name: springboot-live
labels:
app: springboot
spec:
containers:
- name: springboot
image: mydlqclub/springboot-helloworld:0.0.1
imagePullPolicy: IfNotPresent
ports:
- name: server
containerPort: 8080
- name: management
containerPort: 8081
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 8081
path: /actuator/health
livenessProbe:
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 8081
path: /actuator/health
startupProbe:
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 8081
path: /actuator/health

# 先启动探测,成功之后执行就绪和存活,后两个并行执行

# Replicaset

Pod 类型的自主式 pod,但是这存在一个问题,假如 pod 被删除了,那这个 pod 就不能自我恢复,就会彻底被删除,线上这种情况非常危险,pod 的控制器,所谓控制器就是能够管理 pod,监测 pod 运行状况,当 pod 发生故障,可以自动恢复 pod。也就是说能够代我们去管理 pod 中间层,并帮助我们确保每一个 pod 资源始终处于我们所定义或者我们所期望的目标状态,一旦 pod 资源出现故障,那么控制器会尝试重启 pod 或者里面的容器,如果一直重启有问题的话那么它可能会基于某种策略来进行重新布派或者重新编排;如果 pod 副本数量低于用户所定义的目标数量,它也会自动补全;如果多余,也会自动终止 pod 资源。

kubectl explain rs

ReplicaSet 是 kubernetes 中的一种副本控制器,简称 rs,主要作用是控制由其管理的 pod,使 pod 副本的数量始终维持在预设的个数。它的主要作用就是保证一定数量的 Pod 能够在集群中正常运行,它会持续监听这些 Pod 的运行状态,在 Pod 发生故障时重启 pod,pod 数量减少时重新运行新的 Pod 副本。官方推荐不要直接使用 ReplicaSet,用 Deployments 取而代之,Deployments 是比 ReplicaSet 更高级的概念,它会管理 ReplicaSet 并提供很多其它有用的特性,最重要的是 Deployments 支持声明式更新,声明式更新的好处是不会丢失历史变更。所以 Deployment 控制器不直接管理 Pod 对象,而是由 Deployment 管理 ReplicaSet,再由 ReplicaSet 负责管理 Pod 对象。

Replicaset 控制器主要由三个部分组成:

1、用户期望的 pod 副本数:用来定义由这个控制器管控的 pod 副本有几个

2、标签选择器:选定哪些 pod 是自己管理的,如果通过标签选择器选到的 pod 副本数量少于我们指定的数量,需要用到下面的组件

3、pod 资源模板:如果集群中现存的 pod 数量不够我们定义的副本中期望的数量怎么办,需要新建 pod,这就需要 pod 模板,新建的 pod 是基于模板来创建的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
labels:
app: shabi
tier: zhizhang
spec:
replicas: 3
selector:
matchLabels:
tier1: frontend1
# pod 模板
template:
metadata:
labels:
tier1: frontend1
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
readinessProbe:
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
startupProbe:
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
livenessProbe:
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /


[root@prometheus-master replicaset]# kubectl get rs
NAME DESIRED CURRENT READY AGE
frontend 3 3 3 8s

pod名字 = repset名字 - 随机数,所以在template中只需要定义标签,不需要名字
删除pod之后,pod ip会变,会产生一个IP不同的新的pod
repset永远让pod数量维持

[root@prometheus-master replicaset]# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
frontend-8t7jx 1/1 Running 0 56s 10.244.216.104 prometheus-node1 <none> <none>
frontend-qx7dx 1/1 Running 0 56s 10.244.64.21 prometheus-node2 <none> <none>
frontend-s8jgx 1/1 Running 0 56s 10.244.216.103 prometheus-node1 <none> <none>

# pod 动态扩缩容

replicaset 可以实现动态变更副本数和镜像

变更镜像的时候需要手动删除原来的 pod (delete pods) 不会滚动更新,如果增加副本数只需要在原有基础上增加 pod 即可,删除副本是随机删除 pod

ReplicaSet 最核心的功能是可以动态扩容和回缩,如果我们觉得两个副本太少了,想要增加,只需要修改配置文件 replicaset.yaml 里的 replicas 的值即可,原来 replicas: 3,现在变成 replicaset: 4,修改之后,执行如下命令更新:
kubectl apply -f replicaset.yaml

生产环境如果升级,可以删除一个 pod,观察一段时间之后没问题再删除另一个 pod,但是这样需要人工干预多次;实际生产环境一般采用蓝绿发布,原来有一个 rs1,再创建一个 rs2(控制器),通过修改 service 标签,修改 service 可以匹配到 rs2 的控制器,这样才是蓝绿发布,这个也需要我们精心的部署规划,我们有一个控制器就是建立在 rs 之上完成的,叫做 Deployment

# deployment

Deployment 是 kubernetes 中最常用的资源对象,为 ReplicaSet 和 Pod 的创建提供了一种声明式的定义方法,在 Deployment 对象中描述一个期望的状态,Deployment 控制器就会按照一定的控制速率把实际状态改成期望状态,通过定义一个 Deployment 控制器会创建一个新的 ReplicaSet 控制器,通过 ReplicaSet 创建 pod,删除 Deployment 控制器,也会删除 Deployment 控制器下对应的 ReplicaSet 控制器和 pod 资源.

使用 Deployment 而不直接创建 ReplicaSet 是因为 Deployment 对象拥有许多 ReplicaSet 没有的特性,例如滚动升级、金丝雀发布、蓝绿部署和回滚。

声明式定义是指直接修改资源清单 yaml 文件,然后通过 kubectl apply -f 资源清单 yaml 文件,就可以更改资源

Deployment 控制器是建立在 rs 之上的一个控制器,可以管理多个 rs,每次更新镜像版本,都会生成一个新的 rs,把旧的 rs 替换掉,多个 rs 同时存在,但是只有一个 rs 运行。

Deployment 可以使用声明式定义,直接在命令行通过纯命令的方式完成对应资源版本的内容的修改,也就是通过打补丁的方式进行修改;Deployment 能提供滚动式自定义自控制的更新;对 Deployment 来讲,我们在实现更新时还可以实现控制更新节奏和更新逻辑。(先创建还是先删除)

通过 Deployment 对象,你可以做到以下事情:

1、创建 ReplicaSet 和 Pod

2、滚动升级(不停止旧服务的状态下升级)和回滚应用(将应用回滚到之前的版本)

3、平滑地扩容和缩容

4、暂停和继续 Deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@ ~]# kubectl explain deploy.spec.strategy.rollingUpdate
maxSurge <string>
#我们更新的过程当中最多允许超出的指定的目标副本数有几个;
它有两种取值方式,第一种直接给定数量,第二种根据百分比,百分比表示原本是5个,最多可以超出20%,那就允许多一个,最多可以超过40%,那就允许多两个
maxUnavailable <string>
#最多允许几个不可用
假设有5个副本,最多一个不可用,就表示最少有4个可用

假如pod数为5,surge和maxun都是2
那么最大超出的副本数是5+2
最少副本数为5-2
maxsurge 出现小数向上取整
maxunavailable 出现小数向下取整

ctr -n=k8s.io images import x.tar.gz
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
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v1
spec:
replicas: 2
template:
metadata:
initialDelaySeconds: 20
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
livenessProbe:
periodSeconds: 5
initialDelaySeconds: 20
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
readinessProbe:
periodSeconds: 5
initialDelaySeconds: 20
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
selector:
matchLabels:
app: myapp
version: v1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 [root@master1 ~]# kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
myapp-v1 2/2 2 2 57s
[root@master1 ~]# kubectl get replicaset
NAME DESIRED CURRENT READY AGE
myapp-v1-b5d4c8b9d 2 2 2 72s
[root@master1 ~]# kubectl get pods
myapp-v1-b5d4c8b9d-cxcvr 1/1 Running 0 108s
myapp-v1-b5d4c8b9d-ptsv4 1/1 Running 0 108s

1.NAME :列出名称空间中deployment的名称。
2.READY:显示deployment有多少副本数。它遵循ready/desired的模式。
3.UP-TO-DATE: 显示已更新到所需状态的副本数。
4.AVAILABLE: 显示你的可以使用多少个应用程序副本。
5.AGE :显示应用程序已运行的时间。

1.NAME: 列出名称空间中ReplicaSet资源
2.DESIRED:显示应用程序的所需副本数,这些副本数是在创建时定义的。这是所需的状态。
3.CURRENT: 显示当前正在运行多少个副本。
4.READY: 显示你的用户可以使用多少个应用程序副本。
5.AGE :显示应用程序已运行的时间。
1
2
3
4
# 扩容缩容
spec:
replicas: 3
声明式更新 - apply -f

# 滚动更新

滚动更新是一种自动化程度较高的发布方式,用户体验比较平滑,是目前成熟型技术组织所采用的主流发布方式,一次滚动发布一般由若干个发布批次组成,每批的数量一般是可以配置的(可以通过发布模板定义),例如第一批 1 台,第二批 10%,第三批 50%,第四批 100%。每个批次之间留观察间隔,通过手工验证或监控反馈确保没有问题再发下一批次,所以总体上滚动式发布过程是比较缓慢的

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
# rollingupdate
kubectl explain deploy.spec.strategy.rollingUpdate
KIND: Deployment
VERSION: apps/v1
RESOURCE: rollingUpdate <Object>
DESCRIPTION:
Rolling update config params. Present only if DeploymentStrategyType =
RollingUpdate.
Spec to control the desired behavior of rolling update.
FIELDS:
maxSurge <string>
The maximum number of pods that can be scheduled above the desired number
of pods. Value can be an absolute number (ex: 5) or a percentage of desired
pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number
is calculated from percentage by rounding up. Defaults to 25%. Example:
when this is set to 30%, the new ReplicaSet can be scaled up immediately
when the rolling update starts, such that the total number of old and new
pods do not exceed 130% of desired pods. Once old pods have been killed,
new ReplicaSet can be scaled up further, ensuring that total number of pods
running at any time during the update is at most 130% of desired pods.

#我们更新的过程当中最多允许超出的指定的目标副本数有几个;
它有两种取值方式,第一种直接给定数量,第二种根据百分比,百分比表示原本是5个,最多可以超出20%,那就允许多一个,最多可以超过40%,那就允许多两个
maxUnavailable <string>
The maximum number of pods that can be unavailable during the update. Value
can be an absolute number (ex: 5) or a percentage of desired pods (ex:
10%). Absolute number is calculated from percentage by rounding down. This
can not be 0 if MaxSurge is 0. Defaults to 25%. Example: when this is set
to 30%, the old ReplicaSet can be scaled down to 70% of desired pods
immediately when the rolling update starts. Once new pods are ready, old
ReplicaSet can be scaled down further, followed by scaling up the new
ReplicaSet, ensuring that the total number of pods available at all times
during the update is at least 70% of desired pods.
#最多允许几个不可用
假设有5个副本,最多一个不可用,就表示最少有4个可用




# 查看历史版本
[root@master1 ~]# kubectl rollout history deployment myapp-v1
deployment.apps/myapp-v1
REVISION CHANGE-CAUSE
1 <none>
2 <none>

滚动更新后会创建一个新的replicaset
kubectl get rs
NAME DESIRED CURRENT READY AGE
myapp-v1-75fb478d6c 3 3 3 2m7s
myapp-v1-67fd9fc9c8 0 0 0 25m

# 回滚
kubectl rollout undo deployment/myapp-v1 --to-revision=1

# 自动滚动更新策略

maxUnavailable: [0%, 100%] 向下取整,比如 10 个副本,5% 的话 0.5 个,但计算按照 0 个;
maxSurge: [0%, 100%] 向上取整,比如 10 个副本,5% 的话
0.5 个,但计算按照 1 个;

建议配置,不少于目标副本数,但是 maxsurge 变大可以实现更快的更新

  1. maxUnavailable == 0
  2. maxSurge == 1

即 “一上一下,先上后下” 最平滑原则:

maxUnavailable:和期望的副本数比,不可用副本数最大比例(或最大值),这个值越小,越能保证服务稳定,更新越平滑;
maxSurge:和期望的副本数比,超过期望副本数最大比例(或最大值),这个值调的越大,副本更新速度越快。

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
deploy.spec.strategy.type
== Recreate
== RollingUpdate(default)

# 自定义策略 通过命令
kubectl patch deployment myapp-v1 -p '{"spec":{"strategy":{"rollingUpdate": {"maxSurge":1,"maxUnavailable":1}}}}'
# 自定义策略 通过yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v1
spec:
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
replicas: 3
template:
metadata:
labels:
app: myapp
version: v1
# 删除一个pod再新建一个pod


# 重建式方式
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v1
spec:
strategy:
type: Recreate
replicas: 3
template:
metadata:
labels:
app: myapp
version: v1
spec:
containers:
- name: myapp
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
# 先把所有pod干掉在新建
myapp-v1-59c458cb84-zslxg 1/1 Terminating 0 5m3s
myapp-v1-59c458cb84-nvjhq 1/1 Terminating 0 5m24s
myapp-v1-59c458cb84-gglnb 1/1 Terminating 0 5m24s
myapp-v1-59c458cb84-gglnb 1/1 Terminating 0 5m24s
myapp-v1-59c458cb84-nvjhq 1/1 Terminating 0 5m24s
myapp-v1-59c458cb84-zslxg 1/1 Terminating 0 5m3s
myapp-v1-59c458cb84-gglnb 0/1 Terminating 0 5m25s
myapp-v1-59c458cb84-gglnb 0/1 Terminating 0 5m25s
myapp-v1-59c458cb84-gglnb 0/1 Terminating 0 5m25s
myapp-v1-59c458cb84-zslxg 0/1 Terminating 0 5m4s
myapp-v1-59c458cb84-zslxg 0/1 Terminating 0 5m4s
myapp-v1-59c458cb84-zslxg 0/1 Terminating 0 5m4s
myapp-v1-59c458cb84-nvjhq 0/1 Terminating 0 5m25s
myapp-v1-59c458cb84-nvjhq 0/1 Terminating 0 5m25s
myapp-v1-59c458cb84-nvjhq 0/1 Terminating 0 5m25s
# 最好别用reCreate

# 蓝绿部署

蓝绿部署中,一共有两套系统:一套是正在提供服务系统,标记为 “绿色”;另一套是准备发布的系统,标记为 “蓝色”。两套系统都是功能完善的、正在运行的系统,只是系统版本和对外服务情况不同。

开发新版本,要用新版本替换线上的旧版本,在线上的系统之外,搭建了一个使用新版本代码的全新系统。 这时候,一共有两套系统在运行,正在对外提供服务的老系统是绿色系统,新部署的系统是蓝色系统。

image-20230619175716206

用来做发布前测试,测试过程中发现任何问题,可以直接在蓝色系统上修改,不干扰用户正在使用的系统。(注意,两套系统没有耦合的时候才能百分百保证不干扰)
蓝色系统经过反复的测试、修改、验证,确定达到上线标准之后,直接将用户切换到蓝色系统:

image-20230619180146410

切换后的一段时间内,依旧是蓝绿两套系统并存,但是用户访问的已经是蓝色系统。这段时间内观察蓝色系统(新系统)工作状态,如果出现问题,直接切换回绿色系统。

当确信对外提供服务的蓝色系统工作正常,不对外提供服务的绿色系统已经不再需要的时候,蓝色系统正式成为对外提供服务系统,成为新的绿色系统。 原先的绿色系统可以销毁,将资源释放出来,用于部署下一个蓝色系统。

优点:

1、更新过程无需停机,风险较少

2、回滚方便,只需要更改路由或者切换 DNS 服务器,效率较高

缺点:

1、成本较高,需要部署两套环境。如果新版本中基础服务出现问题,会瞬间影响全网用户;如果新版本有问题也会影响全网用户。

2、需要部署两套机器,费用开销大

3、在非隔离的机器(Docker、VM)上操作时,可能会导致蓝绿环境被摧毁风险

4、负载均衡器 / 反向代理 / 路由 / DNS 处理不当,将导致流量没有切换过来情况出现

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
# deployment实现蓝绿部署
# 创建绿色系统
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v2
namespace: blue-green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: v1
template:
metadata:
labels:
app: myapp
version: v1
spec:
containers:
- name: myapp
image: janakiramm/myapp:v1
imagePullPolicy: IfNotPresent
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
# 创建蓝色系统
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-v1
namespace: blue-green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: v2
template:
metadata:
labels:
app: myapp
version: v2
spec:
containers:
- name: myapp
image: janakiramm/myapp:v2
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
# 此时同时有两组pod存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 创建四层代理
apiVersion: v1
kind: Service
metadata:
name: myapp-lan-lv
namespace: blue-green
labels:
app: myapp
spec:
type: NodePort
ports:
- port: 80
nodePort: 30062
name: http
selector:
app: myapp
version: v2
# 蓝绿部署需要service 通过service中selector选择不同label选择不同pod实现service代理到不同deploy,实现动态流量切换
# 切换到蓝色
selector:
app: myapp
version: v2
# 切换到绿色
selector:
app: myapp
version: v1

# 金丝雀发布

金丝雀发布(又称灰度发布、灰度更新):金丝雀发布一般先发 1 台,或者一个小比例,例如 2% 的服务器,主要做流量验证用,也称为金丝雀 (Canary) 测试 (国内常称灰度测试)。

简单的金丝雀测试一般通过手工测试验证,复杂的金丝雀测试需要比较完善的监控基础设施配合,通过监控指标反馈,观察金丝雀的健康状况,作为后续发布或回退的依据。 如果金丝测试通过,则把剩余的 V1 版本全部升级为 V2 版本。如果金丝雀测试失败,则直接回退金丝雀,发布失败。

image-20230619182743417

优点:灵活,策略自定义,可以按照流量或具体的内容进行灰度 (比如不同账号,不同参数),出现问题不会影响全网用户

缺点:没有覆盖到所有的用户导致出现问题不好排查

1
2
3
4
5
6
7
8
9
# 先创建一个deployment
kubectl apply -f .yaml
# 只更新一个pod,此时有n+1个pod,立刻暂停更新,新的pod称为金丝雀
kubectl set image deployment myapp-v1 myapp=nginx -n blue-green && kubectl rollout pause deployment myapp-v1 -n blue-green

上面的解释说明把myapp这个容器的镜像更新到nginx版本 更新镜像之后,创建一个新的pod就立即暂停,这就是我们说的金丝雀发布;如果暂停几个小时之后没有问题,那么取消暂停,就会依次执行后面步骤,把所有pod都升级。

# 全部pod更新,解除暂停
kubectl rollout resume deployment myapp-v1 -n blue-green

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kubectl run mynginx --image=nginx

# 通过命令行创建deployment

kubectl create deployment mytomcat --image=tomcat:8.5.68
# delete 之后会立刻重新起一个新的pod,称为自愈能力

[root@master ~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
mytomcat 1/1 1 1 4m6s


[root@master ~]# kubectl delete deploy mytomcat


# 多副本
kubectl create deployment my-dep --image=nginx --replicas=3

使用 web 页面

image-20230410160308833

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: my-dep
name: my-dep
spec:
replicas: 3
selector:
matchLabels:
app: my-dep
template:
metadata:
labels:
app: my-dep
spec:
containers:
- image: nginx
name: 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
25
26
27
28
29
30
31
32
33
34
35
# 扩缩容
kubectl scale --replicas=5 deployment/my-dep


# 编辑配置文件实现扩缩容
[root@master ~]# kubectl edit deploy my-dep


# 自愈 - 故障转移
自愈: 尝试重启pod
故障转移: 将pod从故障的机器转移到好的机器

# 滚动更新
# 实现不停机更新
[root@master ~]# kubectl set image deployment/my-dep nginx=nginx:1.16.1 --record
[root@master ~]# kubectl rollout status deployment/my-dep

# 查看deploy信息
kubectl get deploy my-dep -oyaml



# 版本回退
#历史记录
kubectl rollout history deployment/my-dep


#查看某个历史详情
kubectl rollout history deployment/my-dep --revision=2

#回滚(回到上次)
kubectl rollout undo deployment/my-dep

#回滚(回到指定版本)
kubectl rollout undo deployment/my-dep --to-revision=2

除了 Deployment,k8s 还有 StatefulSetDaemonSetJob 等 类型资源。我们都称为 工作负载

有状态应用使用 StatefulSet 部署,无状态应用使用 Deployment 部署

其他工作负载

Deployment: 无状态应用部署,比如微服务,提供多副本等功能

StatefulSet: 有状态应用部署,比如 redis,提供稳定的存储、网络等功能

DaemonSet: 守护型应用部署,比如日志收集组件,在每个机器都运行一份

Job/CronJob: 定时任务部署,比如垃圾清理组件,可以在指定时间运行

image-20230410164854497

# service

在 kubernetes 中,Pod 是有生命周期的,如果 Pod 重启它的 IP 很有可能会发生变化。如果我们的服务都是将 Pod 的 IP 地址写死,Pod 挂掉或者重启,和刚才重启的 pod 相关联的其他服务将会找不到它所关联的 Pod,为了解决这个问题,在 kubernetes 中定义了 service 资源对象,Service 定义了一个服务访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,service 是一组 Pod 的逻辑集合,这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector 实现的。

创建 service 的时候会创建同名的 endpoint,会把 pod 的 ip 和端口加入到 endpoint 中

service 创建的时候有 service 的 ip,关联到 pod 的 ip 和端口,都保存在防火墙规则中:iptables 或者 ipvs

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
[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 127.0.0.1:30821 rr
-> 10.244.166.155:80 Masq 1 0 0
-> 10.244.166.156:80 Masq 1 0 0
TCP 127.0.0.1:31180 rr
-> 10.244.166.172:8080 Masq 1 0 0
TCP 127.0.0.1:31181 rr
-> 10.244.166.172:8081 Masq 1 0 0
TCP 127.0.0.1:31441 rr
-> 10.244.180.1:8443 Masq 1 0 0
TCP 172.17.0.1:30821 rr
-> 10.244.166.155:80 Masq 1 0 0
-> 10.244.166.156:80 Masq 1 0 0
TCP 172.17.0.1:31180 rr
-> 10.244.166.172:8080 Masq 1 0 0
TCP 172.17.0.1:31181 rr
-> 10.244.166.172:8081 Masq 1 0 0
TCP 172.17.0.1:31441 rr
-> 10.244.180.1:8443 Masq 1 0 0
TCP 192.168.13.177:30821 rr
-> 10.244.166.155:80 Masq 1 0 0
-> 10.244.166.156:80 Masq 1 0 0


kube-proxy 这个组件将始终监视着 apiserver 中有关 service 资源的变动信息,需要跟 master 之上的 apiserver 交互,随时连接到 apiserver 上获取任何一个与 service 资源相关的资源变动状态,这种是通过 kubernetes 中固有的一种请求方法 watch(监视)来实现的,一旦有 service 资源的内容发生变动(如创建,删除),kube-proxy 都会将它转化成当前节点之上的能够实现 service 资源调度,把我们请求调度到后端特定的 pod 资源之上的规则,这个规则可能是 iptables,也可能是 ipvs,取决于 service 的实现方式。

k8s 在创建 Service 时,会根据标签选择器 selector (lable selector) 来查找 Pod,据此创建与 Service 同名的 endpoint 对象,当 Pod 地址发生变化时,endpoint 也会随之发生变化,service 接收前端 client 请求的时候,就会通过 endpoint,找到转发到哪个 Pod 进行访问的地址。(至于转发到哪个节点的 Pod,由负载均衡 kube-proxy 决定)

kubernetes 集群中有三类 IP 地址

1、Node Network(节点网络):物理节点或者虚拟节点的网络,如 ens33 接口上的网路地址

2、Pod network(pod 网络),创建的 Pod 具有的 IP 地址
Node Network 和 Pod network 这两种网络地址是我们实实在在配置的,其中节点网络地址是配置在节点接口之上,而 pod 网络地址是配置在 pod 资源之上的,因此这些地址都是配置在某些设备之上的,这些设备可能是硬件,也可能是软件模拟的

3、Cluster Network(集群地址,也称为 service network),这个地址是虚拟的地址(virtual ip),没有配置在某个接口上,只是出现在 service 的规则当中。

三个网段不能冲突

–type=ClusterIP 集群内部访问

–type=NodePort 集群外也能访问

–type=ExternalName

–type=LoadBalancer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@ ~]# kubectl explain service.spec.ports
KIND: Service
VERSION: v1
RESOURCE: ports <[]Object>
DESCRIPTION:
The list of ports that are exposed by this service. More info:
https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
ServicePort contains information on service's port.
FIELDS:
appProtocol <string>
name <string> #定义端口的名字
nodePort <integer> #service在物理机映射的端口,默认在 30000-32767 之间
port <integer> -required- #service的端口,这个是k8s集群内部服务可访问的端口
protocol <string>
targetPort <string>
# targetPort是pod上的端口,从port和nodePort上来的流量,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。

# cluster-ip

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
# 默认的type类型
# 创建nginx的deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80 #pod中的容器需要暴露的端口
startupProbe:
periodSeconds: 5
initialDelaySeconds: 60
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
livenessProbe:
periodSeconds: 5
initialDelaySeconds: 60
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
readinessProbe:
periodSeconds: 5
initialDelaySeconds: 60
timeoutSeconds: 10
httpGet:
scheme: HTTP
port: 80
path: /
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: ClusterIP
ports:
- port: 80 #service的端口,暴露给k8s集群内部服务访问
protocol: TCP
targetPort: 80 #pod容器中定义的端口
selector:
run: my-nginx #选择拥有run=my-nginx标签的pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@master1 ~]# kubectl describe svc my-nginx 
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP Families: <none>
IP: 10.96.60.147
IPs: 10.96.60.147
Port: <unset> 30080/TCP
TargetPort: 80/TCP
Endpoints: 10.244.166.180:80,10.244.166.181:80
Session Affinity: None
Events: <none>
You have new mail in /var/spool/mail/root

[root@master1 ~]# kubectl describe ep my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2023-05-10T12:28:47Z
Subsets:
Addresses: 10.244.166.180,10.244.166.181
NotReadyAddresses: <none>
Ports:
Name Port Protocol
---- ---- --------
<unset> 80 TCP

Events: <none>

[root@master1 ~]# ipvsadm -Ln
# 只能在集群内访问


service 只要创建完成,我们就可以直接解析它的服务名,每一个服务创建完成后都会在集群 dns 中动态添加一个资源记录,添加完成后我们就可以解析了,资源记录格式是:

SVC_NAME.NS_NAME.DOMAIN.LTD.
服务名。命名空间。域名后缀
集群默认的域名后缀是 svc.cluster.local.

就像我们上面创建的 my-nginx 这个服务,它的完整名称解析就是
my-nginx.default.svc.cluster.local

这个域名只能 service 关联的 pod 内访问

使用就绪探测防止容器还没能提供服务就加入 endpoint 中,不能通过 service 访问


pod 的发现与负载均衡

将一组 Pods 公开为网络服务的抽象方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@master ~]# kubectl expose deploy my-dep --port=8000 --target-port=80  --type=ClusterIP
service/my-dep exposed
[root@master ~]# kubectl delete service my-dep
[root@master ~]# kubectl get service

[root@master ~]# kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
my-dep-5b7868d854-29j5l 1/1 Running 0 21h app=my-dep,pod-template-hash=5b7868d854
my-dep-5b7868d854-67vg6 1/1 Running 0 21h app=my-dep,pod-template-hash=5b7868d854
my-dep-5b7868d854-9x4j8 1/1 Running 0 21h app=my-dep,pod-template-hash=5b7868d854
my-dep-5b7868d854-jtpbw 1/1 Running 0 21h app=my-dep,pod-template-hash=5b7868d854
my-dep-5b7868d854-nb4kn 1/1 Running 0 21h app=my-dep,pod-template-hash=5b7868d854

在集群内使用IP:port 可以实现负载均衡访问

也可以使用svc域名直接访问,域名规则: 服务名.所在namespace.svc 只能在pod内使用域名访问

curl my-dep.default.svc:8000

root@my-dep-5b7868d854-nb4kn:/# cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
192.168.166.142 my-dep-5b7868d854-nb4kn
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
labels:
app: my-dep
name: my-dep
spec:
selector:
app: my-dep # 选择标签
ports:
- port: 8000
protocol: TCP
targetPort: 80

image-20230411140954966

# NodePort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-nodeport
spec:
selector:
matchLabels:
run: my-nginx-nodeport
replicas: 2
template:
metadata:
labels:
run: my-nginx-nodeport
spec:
containers:
- name: my-nginx-nodeport-container
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport
labels:
run: my-nginx-nodeport
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 30380
selector:
run: my-nginx-nodeport
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@master1 ~]# kubectl get svc 
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-nodeport NodePort 10.96.152.128 <none> 80:30380/TCP 4s

[root@master1 ~]# ss -lntup | grep 30380
tcp LISTEN 0 128 *:30380 *:* users:(("kube-proxy",pid=14706,fd=10))
会在每一个节点上监听这个端口

[root@master1 ~]# kubectl get ep
NAME ENDPOINTS AGE
my-nginx-nodeport 10.244.166.184:80,10.244.166.185:80 5m2s

curl节点中任何一个节点都能请求到

客户端请求http://192.168.13.177:30380->docker0虚拟网卡:172.17.0.1:30380-> 10.244.166.184:80,10.244.166.185:80

image-20230411141906087

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@master ~]# kubectl expose deploy my-dep --port=8000 --target-port=80 --type=NodePort

[root@master ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d17h
my-dep NodePort 10.96.206.111 <none> 8000:32666/TCP 6m59s


# 使用masterip或nodeip + 端口都可以访问
# 容器内部仍可以通过域名访问,主语端口还是原来的8000

32666只是用于外部访问的端口,实现公网访问

# externalName

跨名称空间访问

需求:default 名称空间下的 client 服务想要访问 nginx-ns 名称空间下的 nginx-svc 服务

service 正常情况只能代理自己 ns 下面的 pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: nginx-ns
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: nginx-ns
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 36000"]
---
apiVersion: v1
kind: Service
metadata:
name: client-svc
spec:
type: ExternalName
externalName: nginx-svc.nginx-ns.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80


wget -q -O client-svc

[root@master1 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
client-svc ExternalName <none> nginx-svc.nginx-ns.svc.cluster.local 80/TCP 15s



kubectl exec -it client-76b6556d97-xk7mg -- /bin/sh
/ # wget -q -O - client-svc
wget -q -O - nginx-svc.nginx-ns.svc.cluster.local
上面两个请求的结果一样

此时client-svc相当于 nginx-svc.nginx-ns.svc.cluster.local的软链,可以不使用名称空间直接访问软链的资源
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
# 映射外部服务
# k8s引用集群外的mysql
# 工作节点安装mysql
https://zhuanlan.zhihu.com/p/623778183


apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
type: ClusterIP
ports:
- port: 3306

# 没有selector找不到endpoint
[root@master1 ~]# kubectl describe svc mysql
Name: mysql
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Families: <none>
IP: 10.96.96.21
IPs: 10.96.96.21
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: <none>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 手动创建EP
apiVersion: v1
kind: Endpoints
metadata:
name: mysql # 和service name一致
subsets:
- addresses:
- ip: 192.168.13.199
ports:
- port: 3306


[root@master1 ~]# kubectl describe svc mysql
Name: mysql
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP Families: <none>
IP: 10.96.159.210
IPs: 10.96.159.210
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: 192.168.13.199:3306
Session Affinity: None
Events: <none>

# coredns

CoreDNS 其实就是一个 DNS 服务,而 DNS 作为一种常见的服务发现手段,所以很多开源项目以及工程师都会使用 CoreDNS 为集群提供服务发现的功能,Kubernetes 就在集群中使用 CoreDNS 解决服务发现的问题。

service fqdn == service_name.namespace.svc.cluster.local

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
apiVersion: v1
kind: Pod
metadata:
name: dig
namespace: default
spec:
containers:
- name: dig
image: dig
imagePullPolicy: IfNotPresent
command:
- sleep
- "3600"
restartPolicy: Always

[root@] ~]# kubectl exec -it dig -- nslookup kubernetes
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: kubernetes.default.svc.cluster.local
Address: 10.96.0.1

在k8s中创建service之后,service默认的FQDN是<servicename>.<namespace>.svc.cluster.local,那么k8s集群内部的服务就可以通过FQDN访问

[root@prometheus-master service]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 16d

[root@prometheus-master service]# kubectl describe svc -n kube-system kube-dns
Name: kube-dns
Namespace: kube-system
IP Families: IPv4
IP: 10.96.0.10
IPs: 10.96.0.10
Port: dns 53/UDP
TargetPort: 53/UDP
Endpoints: 10.244.229.92:53,10.244.229.93:53
Port: dns-tcp 53/TCP
TargetPort: 53/TCP
Endpoints: 10.244.229.92:53,10.244.229.93:53
Port: metrics 9153/TCP
TargetPort: 9153/TCP
Endpoints: 10.244.229.92:9153,10.244.229.93:9153

# coredns 实现service域名解析
coredns-6d8c4cb4d-jmstd 1/1 Running 5 (30h ago) 16d 10.244.229.93 prometheus-master <none> <none>
coredns-6d8c4cb4d-pg4sb 1/1 Running 5 (30h ago) 16d 10.244.229.92 prometheus-master <none> <none>


coredns 也可以解析外部域名
[root@xianchaomaster1 ~]# kubectl exec -it dig -- nslookup www.baidu.com

# ingress

Service 可以通过标签选择器找到它所关联的 Pod。但是属于四层代理,只能基于 IP 和端口代理。

Service 的 type 类型有很多,如 NodePort、clusterIp、loadbalancer、externalname,如果 Service 想要被 k8s 集群外部访问,需要用 NodePort 类型,但是 NodePort 类型的 svc 有如下几个问题:
nodeport 会在物理机映射一个端口,绑定到物理机上,这样就导致,每个服务都要映射一个端口,端口过多,维护困难,还有就是 Service 底层使用的是 iptables 或者 ipvs,仅支持四层代理,无法基于 https 协议做代理

四层负载和七层负载的区别:
区别:
1)四层负载:四层的负载均衡就是基于 IP + 端口的负载均衡:在三层负载均衡的基础上,通过发布三层的 IP 地址(VIP),然后加四层的端口号,来决定哪些流量需要做负载均衡,对需要处理的流量进行 NAT 处理,转发至后台服务器,并记录下这个 TCP 或者 UDP 的流量是由哪台服务器处理的,后续这个连接的所有流量都同样转发到同一台服务器处理。

2)七层的负载均衡就是基于虚拟的 URL 或主机 IP 的负载均衡:在四层负载均衡的基础上(没有四层是绝对不可能有七层的),再考虑应用层的特征,比如同一个 Web 服务器的负载均衡,除了根据 VIP 加 80 端口辨别是否需要处理的流量,还可根据七层的 URL、浏览器类别、语言来决定是否要进行负载均衡。举个例子,如果你的 Web 服务器分成两组,一组是中文语言的,一组是英文语言的,那么七层负载均衡就可以当用户来访问你的域名时,自动辨别用户语言,然后选择对应的语言服务器组进行负载均衡处理。

3)四层负载均衡工作在传输层,七层负载均衡工作在应用层

ingress 是 k8s 中的资源,主要是管理 ingress-controller 这个代理的配置文件

Ingress Controller 是一个七层负载均衡调度器,客户端的请求先到达这个七层负载均衡调度器,由七层负载均衡器在反向代理到后端 pod,常见的七层负载均衡器有 nginx、traefik,以我们熟悉的 nginx 为例,假如请求到达 nginx,会通过 upstream 反向代理到后端 pod 应用,但是后端 pod 的 ip 地址是一直在变化的,因此在后端 pod 前需要加一个 service,这个 service 只是起到分组的作用,那么我们 upstream 只需要填写 service 地址即可。

ingress-controller 封装的 nginx,ingress 维护配置,ingress 创建好之后,会自动的把配置文件传到 ingress-controller 这个 pod 里,会自动进行 reload,然后配置就生效了。

Ingress Controller
Ingress Controller 结合 Ingress 定义的规则生成配置,然后动态更新 ingress-controller 里的 Nginx 或者 trafik 负载均衡器,并刷新使配置生效,来达到服务自动发现的作用。
Ingress 则是定义规则,通过它定义某个域名的请求过来之后转发到集群中指定的 Service。它可以通过 Yaml 文件定义,可以给一个或多个 Service 定义一个或多个 Ingress 规则。

使用 Ingress Controller 代理 k8s 内部 pod 的流程

(1)部署 Ingress controller,我们 ingress controller 使用的是 nginx
(2)创建 Pod 应用,可以通过控制器创建 pod
(3)创建 Service,用来分组 pod
(4)创建 Ingress http,测试通过 http 访问应用
(5)创建 Ingress https,测试通过 https 访问应用
客户端通过七层调度器访问后端 pod 的方式

# ingress 高可用

Ingress Controller 是集群流量的接入层,对它做高可用非常重要,可以基于 keepalive 实现 nginx-ingress-controller 高可用,具体实现如下:
Ingress-controller 根据 Deployment+ nodeSeletor+pod 反亲和性方式部署在 k8s 指定的两个 work 节点,nginx-ingress-controller 这个 pod 共享宿主机 ip,然后通过 keepalive+nginx 实现 nginx-ingress-controller 高可用

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
[root@node1 ~]# ctr -n=k8s.io images import ingress-nginx-controllerv1.1.0.tar.gz
[root@node1 ~]# ctr -n=k8s.io images import kube-webhook-certgen-v1.1.0.tar.gz
# kubectl apply -f ingress-deploy.yaml

[root@master1 ingress]# kubectl get pod -n ingress-nginx -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ingress-nginx-admission-create-g4qzz 0/1 Completed 0 8m2s 10.244.166.180 node1 <none> <none>
ingress-nginx-admission-patch-r62tp 0/1 Completed 1 8m2s 10.244.166.181 node1 <none> <none>
ingress-nginx-controller-6c8ffbbfcf-28tmg 1/1 Running 0 82s 192.168.13.199 node1 <none> <none>

# webhook报错,k8s版本 > 1.25 报错
kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission

# 安装nginx,keepalived,并修改配置,上传check_nginx.sh
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

#include /usr/share/nginx/modules/*.conf;

events {
worker_connections 1024;
}

# 四层负载均衡,为两台Master apiserver组件提供负载均衡
stream {

log_format main '$remote_addr $upstream_addr - [$time_local] $status $upstream_bytes_sent';

access_log /var/log/nginx/k8s-access.log main;

upstream k8s-ingress-controller{
server 192.168.40.181:80 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.40.182:80 weight=5 max_fails=3 fail_timeout=30s;
}

server {
listen 30080;
proxy_pass k8s-ingress-controller;
}
}

http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

include /etc/nginx/mime.types;
default_type application/octet-stream;

}

3、keepalive配置
主keepalived
[root@node1 ~]# cat /etc/keepalived/keepalived.conf
global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id NGINX_MASTER
}

vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
}

vrrp_instance VI_1 {
state MASTER
interface ens33 # 修改为实际网卡名
virtual_router_id 51 # VRRP 路由 ID实例,每个实例是唯一的
priority 100 # 优先级,备服务器设置 90
advert_int 1 # 指定VRRP 心跳包通告间隔时间,默认1秒
authentication {
auth_type PASS
auth_pass 1111
}
# 虚拟IP
virtual_ipaddress {
192.168.40.199/24
}
track_script {
check_nginx
}
}

#vrrp_script:指定检查nginx工作状态脚本(根据nginx状态判断是否故障转移)
#virtual_ipaddress:虚拟IP(VIP)

[root@node1 ~]# cat /etc/keepalived/check_nginx.sh
#!/bin/bash
#1、判断Nginx是否存活
counter=$(ps -ef |grep nginx | grep sbin | egrep -cv "grep|$$" )
if [ $counter -eq 0 ]; then
    #2、如果不存活则尝试启动Nginx
    service nginx start
    sleep 2
    #3、等待2秒后再次获取一次Nginx状态
    counter=$(ps -ef |grep nginx | grep sbin | egrep -cv "grep|$$" )
    #4、再次进行判断,如Nginx还不存活则停止Keepalived,让地址进行漂移
    if [ $counter -eq 0 ]; then
        service  keepalived stop
    fi
fi

[root@node1 ~]# chmod +x /etc/keepalived/check_nginx.sh

备keepalive
[root@node2 ~]# cat /etc/keepalived/keepalived.conf
global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id NGINX_BACKUP
}

vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
}

vrrp_instance VI_1 {
state BACKUP
interface ens33
virtual_router_id 51 # VRRP 路由 ID实例,每个实例是唯一的
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.1.199/24
}
track_script {
check_nginx
}
}


[root@node2 ~]# cat /etc/keepalived/check_nginx.sh
#!/bin/bash
#1、判断Nginx是否存活
counter=$(ps -ef |grep nginx | grep sbin | egrep -cv "grep|$$" )
if [ $counter -eq 0 ]; then
    #2、如果不存活则尝试启动Nginx
    service nginx start
    sleep 2
    #3、等待2秒后再次获取一次Nginx状态
    counter=$(ps -ef |grep nginx | grep sbin | egrep -cv "grep|$$" )
    #4、再次进行判断,如Nginx还不存活则停止Keepalived,让地址进行漂移
    if [ $counter -eq 0 ]; then
        service  keepalived stop
    fi
fi


[root@node2 ~]# chmod +x /etc/keepalived/check_nginx.sh
#注:keepalived根据脚本返回状态码(0为工作正常,非0不正常)判断是否故障转移。

4、启动服务:
[root@node1 ~]# systemctl daemon-reload
[root@node1 ~]# systemctl enable nginx keepalived
[root@node1 ~]# systemctl start nginx
[root@node1 ~]# systemctl start keepalived

4、启动服务:
[root@node2 ~]# systemctl daemon-reload
[root@node2 ~]# systemctl enable nginx keepalived
[root@node2 ~]# systemctl start nginx
[root@node2 ~]# systemctl start keepalived

5、测试vip是否绑定成功
[root@node1 ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:79:9e:36 brd ff:ff:ff:ff:ff:ff
inet 192.168.40.182/24 brd 192.168.40.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet 192.168.40.199/24 scope global secondary ens33
valid_lft forever preferred_lft forever
inet6 fe80::b6ef:8646:1cfc:3e0c/64 scope link noprefixroute
valid_lft forever preferred_lft forever



systemctl daemon-reload
systemctl enable nginx keepalived
systemctl start nginx
systemctl start keepalived

(85 条消息) REK 安装 K8S 后,Ingress-nginx 一直状态为 ContainerCreating_ingress-nginx-controller 一直是 containercreating_Steady Ben 的博客 - CSDN 博客

# http 代理 pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
# http
apiVersion: v1
kind: Service
metadata:
name: tomcat
namespace: default
spec:
selector:
app: tomcat
release: canary
ports:
- name: http
targetPort: 8080
port: 8080
- name: ajp
targetPort: 8009
port: 8009
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: tomcat
release: canary
template:
metadata:
labels:
app: tomcat
release: canary
spec:
containers:
- name: tomcat
image: tomcat:9.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
name: ajp
containerPort: 8009
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-myapp
namespace: default
annotations:
#kubernetes.io/ingress.class: "nginx" # 标识ingress-controller 只能在1.23以下使用,1.25需要使用IngressClassName
spec:
ingressClassName: nginx
rules:
- host: tomcat.shabi.com
http:
paths:
- backend:
service:
name: tomcat
port:
number: 8080
path: /
pathType: Prefix

[root@master1 ingress]# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-myapp nginx tomcat.shabi.com 192.168.13.199 80 3m29s
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
# https
openssl genrsa -out tls.key 2048
openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=hhh.com
kubectl create secret tls tomcat-ingress-secret --cert=tls.crt --key=tls.key

[root@master1]# cd /root/
[root@master1 ~]# openssl genrsa -out tls.key 2048
[root@master1 ~]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=han.lucky.com
(2)生成secret,在master1节点操作
[root@master1 ~]# kubectl create secret tls tomcat-ingress-secret --cert=tls.crt --key=tls.key

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-tomcat-tls
namespace: default
spec:
ingressClassName: nginx
tls:
- hosts:
- tomcat.shabi.com
secretName: tomcat-ingress-secret
rules:
- host: tomcat.shabi.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tomcat
port:
number: 8080

# ingress 实现业务灰度发布

假设线上运行了一套对外提供 7 层服务的 Service A 服务,后来开发了个新版本 Service A’ 想要上线,但又不想直接替换掉原来的 Service A,希望先灰度一小部分用户,等运行一段时间足够稳定了再逐渐全量上线新版本,最后平滑下线旧版本。这个时候就可以利用 Nginx Ingress 基于 Header 或 Cookie 进行流量切分的策略来发布,业务使用 Header 或 Cookie 来标识不同类型的用户,我们通过配置 Ingress 来实现让带有指定 Header 或 Cookie 的请求被转发到新版本,其它的仍然转发到旧版本,从而实现将新版本灰度给部分用户:

image-20230627193032931

假设线上运行了一套对外提供 7 层服务的 Service B 服务,后来修复了一些问题,需要灰度上线一个新版本 Service B’,但又不想直接替换掉原来的 Service B,而是让先切 10% 的流量到新版本,等观察一段时间稳定后再逐渐加大新版本的流量比例直至完全替换旧版本,最后再滑下线旧版本,从而实现切一定比例的流量给新版本:

image-20230627193056866

Ingress-Nginx 是一个 K8S ingress ingress-Nginx 是一个 K8S ingress 工具,支持配置 Ingress Annotations 来实现不同场景下的灰度发布和测试。 Nginx Annotations 支持以下几种 Canary 规则:

假设我们现在部署了两个版本的服务,老版本和 canary 版本工具,支持配置 Ingress Annotations 来实现不同场景下的灰度发布和测试。 Nginx Annotations 支持以下几种 Canary 规则:

假设我们现在部署了两个版本的服务,老版本和 canary 版本

nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为 always 时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为 never 时,请求不会被发送到 Canary 入口。

nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。

nginx.ingress.kubernetes.io/canary-weight:基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求。权重为 60 意味着 60% 流量转到 canary。权重为 100 意味着所有请求都将被发送到 Canary 入口。

nginx.ingress.kubernetes.io/canary-by-cookie:基于 Cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的 cookie。当 cookie 值设置为 always 时,它将被路由到 Canary 入口;当 cookie 值设置为 never 时,请求不会被发送到 Canary 入口。

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
# v1
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v1
spec:
replicas: 1
selector:
matchLabels:
app: nginx
version: v1
template:
metadata:
labels:
app: nginx
version: v1
spec:
containers:
- name: nginx
image: "openresty/openresty:centos"
imagePullPolicy: IfNotPresent
ports:
- name: http
protocol: TCP
containerPort: 80
volumeMounts:
- mountPath: /usr/local/openresty/nginx/conf/nginx.conf
name: config
subPath: nginx.conf
volumes:
- name: config
configMap:
name: nginx-v1
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app: nginx
version: v1
name: nginx-v1
data:
nginx.conf: |-
worker_processes 1;
events {
accept_mutex on;
multi_accept on;
use epoll;
worker_connections 1024;
}
http {
ignore_invalid_headers off;
server {
listen 80;
location / {
access_by_lua '
local header_str = ngx.say("nginx-v1")
';
}
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v1
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
name: http
selector:
app: nginx
version: v1


# v2
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v2
spec:
replicas: 1
selector:
matchLabels:
app: nginx
version: v2
template:
metadata:
labels:
app: nginx
version: v2
spec:
containers:
- name: nginx
image: "openresty/openresty:centos"
imagePullPolicy: IfNotPresent
ports:
- name: http
protocol: TCP
containerPort: 80
volumeMounts:
- mountPath: /usr/local/openresty/nginx/conf/nginx.conf
name: config
subPath: nginx.conf
volumes:
- name: config
configMap:
name: nginx-v2
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app: nginx
version: v2
name: nginx-v2
data:
nginx.conf: |-
worker_processes 1;
events {
accept_mutex on;
multi_accept on;
use epoll;
worker_connections 1024;
}
http {
ignore_invalid_headers off;
server {
listen 80;
location / {
access_by_lua '
local header_str = ngx.say("nginx-v2")
';
}
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v2
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
name: http
selector:
app: nginx
version: v2


# ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v1
port:
number: 80

curl -H "Host: canary.example.com" http://192.168.13.199
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
# 基于 Header 的流量切分:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "Region"
nginx.ingress.kubernetes.io/canary-by-header-pattern: "cd|sz"
name: nginx-canary
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v2
port:
number: 80


curl -H "Host: canary.example.com" -H "Region: cd" http://192.168.13.199 //v2
curl -H "Host: canary.example.com" -H "Region: sz" http://192.168.13.199 //v2
curl -H "Host: canary.example.com" -H "Region: cc" http://192.168.13.199 //v1
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
# 基于 Cookie 的流量切分:
与前面 Header 类似,不过使用 Cookie 就无法自定义 value 了,这里以模拟灰度成都地域用户为例,仅将带有名为 user_from_cd cookie 的请求转发给当前 Canary Ingress 。先删除前面基于 Header 的流量切分的 Canary Ingress,然后创建下面新的 Canary Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: "user_from_cd"
name: nginx-canary
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v2
port:
number: 80
curl -s -H "Host: canary.example.com" --cookie "user_from_cd=always" http://192.168.13.199


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
# 基于服务权重的流量切分
基于服务权重的 Canary Ingress 直接定义需要导入的流量比例,这里以导入 10% 流量到 v2 版本为例 (如果有,先删除之前的 Canary Ingress):

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
name: nginx-canary
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v2
port:
number: 80

for i in {1..10}; do curl -H "Host: canary.example.com" http://192.168.13.199; done;
1
2
3
# ingress service 的统一网关入口
# service pod的统一入口

image-20230411143640824

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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
# 安装ingress
# 使用如下yaml
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx

---
# Source: ingress-nginx/templates/controller-serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
automountServiceAccountToken: true
---
# Source: ingress-nginx/templates/controller-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
data:
---
# Source: ingress-nginx/templates/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
name: 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:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io # k8s 1.14+
resources:
- ingressclasses
verbs:
- get
- list
- watch
---
# Source: ingress-nginx/templates/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
name: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- namespaces
verbs:
- get
- apiGroups:
- ''
resources:
- configmaps
- pods
- secrets
- endpoints
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- extensions
- networking.k8s.io # k8s 1.14+
resources:
- ingresses/status
verbs:
- update
- apiGroups:
- networking.k8s.io # k8s 1.14+
resources:
- ingressclasses
verbs:
- get
- list
- watch
- apiGroups:
- ''
resources:
- configmaps
resourceNames:
- ingress-controller-leader-nginx
verbs:
- get
- update
- apiGroups:
- ''
resources:
- configmaps
verbs:
- create
- apiGroups:
- ''
resources:
- events
verbs:
- create
- patch
---
# Source: ingress-nginx/templates/controller-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx
subjects:
- kind: ServiceAccount
name: ingress-nginx
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/controller-service-webhook.yaml
apiVersion: v1
kind: Service
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller-admission
namespace: ingress-nginx
spec:
type: ClusterIP
ports:
- name: https-webhook
port: 443
targetPort: webhook
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
annotations:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
---
# Source: ingress-nginx/templates/controller-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
revisionHistoryLimit: 10
minReadySeconds: 0
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
dnsPolicy: ClusterFirst
containers:
- name: controller
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/ingress-nginx-controller:v0.46.0
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:8443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LD_PRELOAD
value: /usr/local/lib/libmimalloc.so
livenessProbe:
failureThreshold: 5
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
resources:
requests:
cpu: 100m
memory: 90Mi
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
---
# Source: ingress-nginx/templates/admission-webhooks/validating-webhook.yaml
# before changing this value, check the required kubernetes version
# https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#prerequisites
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
name: ingress-nginx-admission
webhooks:
- name: validate.nginx.ingress.kubernetes.io
matchPolicy: Equivalent
rules:
- apiGroups:
- networking.k8s.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- ingresses
failurePolicy: Fail
sideEffects: None
admissionReviewVersions:
- v1
- v1beta1
clientConfig:
service:
namespace: ingress-nginx
name: ingress-nginx-controller-admission
path: /networking/v1beta1/ingresses
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
rules:
- apiGroups:
- admissionregistration.k8s.io
resources:
- validatingwebhookconfigurations
verbs:
- get
- update
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
rules:
- apiGroups:
- ''
resources:
- secrets
verbs:
- get
- create
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ingress-nginx-admission
annotations:
helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
name: ingress-nginx-admission
namespace: ingress-nginx
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-createSecret.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-create
annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
spec:
template:
metadata:
name: ingress-nginx-admission-create
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: create
image: docker.io/jettech/kube-webhook-certgen:v1.5.1
imagePullPolicy: IfNotPresent
args:
- create
- --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
- --namespace=$(POD_NAMESPACE)
- --secret-name=ingress-nginx-admission
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
securityContext:
runAsNonRoot: true
runAsUser: 2000
---
# Source: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: ingress-nginx-admission-patch
annotations:
helm.sh/hook: post-install,post-upgrade
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
namespace: ingress-nginx
spec:
template:
metadata:
name: ingress-nginx-admission-patch
labels:
helm.sh/chart: ingress-nginx-3.33.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.47.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: admission-webhook
spec:
containers:
- name: patch
image: docker.io/jettech/kube-webhook-certgen:v1.5.1
imagePullPolicy: IfNotPresent
args:
- patch
- --webhook-name=ingress-nginx-admission
- --namespace=$(POD_NAMESPACE)
- --patch-mutating=false
- --secret-name=ingress-nginx-admission
- --patch-failure-policy=Fail
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
restartPolicy: OnFailure
serviceAccountName: ingress-nginx-admission
securityContext:
runAsNonRoot: true
runAsUser: 2000


# 创建 ingress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@master ~]# kubectl apply -f ingress.yml
[root@master ~]# kubectl get service -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
calico-system calico-typha ClusterIP 10.96.222.97 <none> 5473/TCP 2d17h
default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d17h
default my-dep NodePort 10.96.206.111 <none> 8000:32666/TCP 22m
ingress-nginx ingress-nginx-controller NodePort 10.96.18.49 <none> 80:31625/TCP,443:31507/TCP 112s
ingress-nginx ingress-nginx-controller-admission ClusterIP 10.96.19.25 <none> 443/TCP 112s

ingress 就是一个nginx


https://139.198.179.44:31507/
http://139.198.179.44:31625
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
# ingress.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-server
spec:
replicas: 2
selector:
matchLabels:
app: hello-server
template:
metadata:
labels:
app: hello-server
spec:
containers:
- name: hello-server
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/hello-server
ports:
- containerPort: 9000
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-demo
name: nginx-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx-demo
template:
metadata:
labels:
app: nginx-demo
spec:
containers:
- image: nginx
name: nginx
---
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-demo
name: nginx-demo
spec:
selector:
app: nginx-demo
ports:
- port: 8000
protocol: TCP
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
labels:
app: hello-server
name: hello-server
spec:
selector:
app: hello-server
ports:
- port: 8000
protocol: TCP
targetPort: 9000
1
eyJhbGciOiJSUzI1NiIsImtpZCI6IkY5LU9Jc0pnblNNOVJhM1dlWVl6c09DTjNnbFNQa1hzcWhOSThHVENEdlUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLXp3NmRzIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkOTRmMDZlNi1kNTkxLTRmMzktODMxNC0zNzI2MDVhM2RkN2MiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.TMTc8QDTIo3qhsGTYD05c8txOyq2op7xW9ZQB5ICVw5x4fg-L0iUnn1jGyo4REP6ci63zBR-xmirqHy1eQ5GgT1nwGZnv4oKqNElm8-wkAit5rEgVN7ATo5GN3VrRQgNyBsHwqFhe1SsrK47flCZcmBvWbgmU4ugFfrNZ8xHkSURKrLAPUPjsYUC6ugyrEMqfwhcxzMiZcSKxG20jjLgVK7Pd_T01NAObUb_lCjq7mrEV0KdHcYTtSYwVwV88mTu5vwb39k-10V0s6HOqiDx87lp7fPtctrcR3mRiT1PkEvsyhCb8qAuFDPuaJZtESFu2dCXP_fVmv5g7AhSO_qUHg

# 域名访问

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

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-host-bar
spec:
ingressClassName: nginx
rules:
- host: "hello.atguigu.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: hello-server
port:
number: 8000
- host: "demo.atguigu.com"
http:
paths:
- pathType: Prefix
path: "/nginx" # 把请求会转给下面的服务,下面的服务一定要能处理这个路径,不能处理就是404
backend:
service:
name: nginx-demo ## java,比如使用路径重写,去掉前缀nginx
port:
number: 8000

# 路径重写

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
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
name: ingress-host-bar
spec:
ingressClassName: nginx
rules:
- host: "hello.atguigu.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: hello-server
port:
number: 8000
- host: "demo.atguigu.com"
http:
paths:
- pathType: Prefix
path: "/nginx(/|$)(.*)" # 把请求会转给下面的服务,下面的服务一定要能处理这个路径,不能处理就是404
backend:
service:
name: nginx-demo ## java,比如使用路径重写,去掉前缀nginx
port:
number: 8000

# 流量限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-limit-rate
annotations:
nginx.ingress.kubernetes.io/limit-rps: "1"
spec:
ingressClassName: nginx
rules:
- host: "haha.atguigu.com"
http:
paths:
- pathType: Exact
path: "/"
backend:
service:
name: nginx-demo
port:
number: 8000

image-20230411153649369

# 存储抽象

在 k8s 中部署的应用都是以 pod 容器的形式运行的,假如我们部署 MySQL、Redis 等数据库,需要对这些数据库产生的数据做备份。因为 Pod 是有生命周期的,如果 pod 不挂载数据卷,那 pod 被删除或重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到 pod 数据持久化存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@master1 ~]# kubectl explain pod.spec.volumes
awsElasticBlockStore <Object>
azureDisk <Object>
azureFile <Object>
cephfs <Object>
cinder <Object>
configMap <Object>
csi <Object>
downwardAPI <Object>
emptyDir <Object>
ephemeral <Object>
fc <Object>
flexVolume <Object>
flocker <Object>
gcePersistentDisk <Object>
gitRepo <Object>
glusterfs <Object>
hostPath <Object>
iscsi <Object>
name <string> -required-
nfs <Object>
persistentVolumeClaim <Object>
photonPersistentDisk <Object>
portworxVolume <Object>
projected <Object>
quobyte <Object>
rbd <Object>
scaleIO <Object>
secret <Object>
storageos <Object>
vsphereVolume <Object>

常用的如下:
emptyDir 临时目录
hostPath
nfs
persistentVolumeClaim
glusterfs
cephfs
configMap
secret

我们想要使用存储卷,需要经历如下步骤

1、定义 pod 的 volume,这个 volume 指明它要关联到哪个存储上的

2、在容器中要使用 volumemounts 挂载对应的存储

# emptydir

emptyDir 类型的 Volume 是在 Pod 分配到 Node 上时被创建,Kubernetes 会在 Node 上自动分配一个目录,因此无需指定宿主机 Node 上对应的目录文件。 这个目录的初始内容为空,当 Pod 从 Node 上移除时,emptyDir 中的数据会被永久删除。emptyDir Volume 主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。

emptyDir 的一些用途:

缓存空间,例如基于磁盘的归并排序。

为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。

在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: pod-emptydir
spec:
containers:
- name: container-emptydir
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: cache-volume
mountPath: /cache
volumes:
- emptyDir: {}
name: cache-volume


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 ~]# kubectl describe pod pod-emptydir 
Name: pod-emptydir
Namespace: default
Node: node1/192.168.13.199
Start Time: Thu, 18 May 2023 14:15:22 +0800
Labels: <none>
Status: Running
IP: 10.244.166.189
IPs:
IP: 10.244.166.189
Containers:
container-emptydir:
Container ID: docker://4496b67a7a687a878e8f644d2064e45e6a1356cb7fc84b98093d6d37bb281a2a
Image: nginx
Image ID: docker-pullable://nginx@sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31
Port: <none>
Host Port: <none>
State: Running
Started: Thu, 18 May 2023 14:15:24 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/cache from cache-volume (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-sc5zb (ro)
Volumes:
cache-volume:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
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
# emptydir 会在宿主机上创建一个名字为pod uid的临时目录,pod被调度到的节点都会创建这个目录
[root@master1 ~]# kubectl get pod pod-emptydir -oyaml | grep uid
uid: d952eb75-575e-4bf3-b08f-c7faed4f4bbd

[root@node1 ~]# tree /var/lib/kubelet/pods/d952eb75-575e-4bf3-b08f-c7faed4f4bbd
/var/lib/kubelet/pods/d952eb75-575e-4bf3-b08f-c7faed4f4bbd
├── containers
   └── container-emptydir
   └── 21760842
├── etc-hosts
├── plugins
   └── kubernetes.io~empty-dir
   ├── cache-volume
      └── ready
   └── wrapped_default-token-sc5zb
   └── ready
└── volumes
├── kubernetes.io~empty-dir
   └── cache-volume # pod内部文件存储路径
└── kubernetes.io~secret
└── default-token-sc5zb
├── ca.crt -> ..data/ca.crt
├── namespace -> ..data/namespace
└── token -> ..data/token

11 directories, 7 files

# hostpath

hostPath Volume 是指 Pod 挂载宿主机上的目录或文件。 hostPath Volume 使得容器可以使用宿主机的文件系统进行存储,hostpath(宿主机路径):节点级别的存储卷,在 pod 被删除,这个存储卷还是存在的,不会被删除,所以只要同一个 pod 被调度到同一个节点上来,在 pod 被删除重新被调度到这个节点之后,对应的数据依然是存在的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: v1
kind: Pod
metadata:
name: test-hostpath
spec:
containers:
- name: test-nginx
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: test-volume
mountPath: /test-nginx
- name: test-tomcat
image: tomcat:9.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: test-volume
mountPath: /test-tomcat
volumes:
- hostPath:
type: DirectoryOrCreate
path: /data1
name: test-volume

[root@master1 ~]# kubectl exec -it test-hostpath -c test-nginx -- /bin/bash
root@test-hostpath:/# cd /test-nginx/

https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/

不会删除宿主机目录,但是如果调度到不同node还是会丢失数据 nodeName: node2
image-20230518151050405

可以用分布式存储:

nfs,cephfs,glusterfs

# nfs

NFS:网络文件系统,英文 Network File System (NFS),是由 SUN 公司研制的 UNIX 表示层协议 (presentation layer protocol),能使使用者访问网络上别处的文件就像在使用自己的计算机一样。

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
[root@master1 ~]# yum install nfs-utils -y
#在宿主机创建NFS需要的共享目录
[root@master1 ~]# mkdir /data/volumes -pv
mkdir: created directory ‘/data’
mkdir: created directory ‘/data/volumes’
#配置nfs共享服务器上的/data/volumes目录
[root@master1 ~]# systemctl start nfs
[root@master1 ~]# vim /etc/exports
/data/volumes *(rw,no_root_squash)
#rw 该主机对该共享目录有读写权限
# no_root_squash 登入 NFS 主机使用分享目录的使用者,如果是 root 的话,那么对于这个分享的目录来说,他就具有 root 的权限
exportfs -avr

[root@master1 ~]# service nfs restart
#设置成开机自启动
[root@master1 ~]# systemctl enable nfs
#查看nfs是否启动成功
[root@master1 ~]# systemctl status nfs

#node2和node1上也安装nfs驱动
yum install nfs-utils -y
systemctl enable nfs –now
# node节点测试
showmount -e
mount 192.168.13.177:/data/volumes /test/
df -h
umount /test
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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-test
spec:
replicas: 2
selector:
matchLabels:
cunchu: nfs
template:
metadata:
labels:
cunchu: nfs
spec:
containers:
- image: nginx
name: test-nfs
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- name: nfs-volumes
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-volumes
nfs:
server: 192.168.13.177
path: /data/volumes


[root@master1 ~]# cd /data/volumes/
[root@master1 volumes]# vim index.html
<h1>woshishabi<h1>

[root@master1 volumes]# curl 10.244.166.191
<h1>woshishabi</h1>

删除pod,pod重新创建后仍然存在挂载目录中的内容

#上面说明挂载nfs存储卷成功了,nfs支持多个客户端挂载,可以创建多个pod,挂载同一个nfs服务器共享出来的目录;但是nfs如果宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分布式存储有glusterfs和cephfs

假设我们使用 nfs 作为存储层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装nfs
yum install -y nfs-utils


# 主节点
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports

mkdir -p /nfs/data
systemctl enable rpcbind --now
systemctl enable nfs-server --now
#配置生效
exportfs -r


# 从节点
showmount -e 172.31.0.4

#执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /root/nfsmount
mkdir -p /nfs/data

mount -t nfs 172.31.0.4:/nfs/data /nfs/data
# 写入一个测试文件
echo "hello nfs server" > /nfs/data/test.txt

# 原生方式挂载

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
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-pv-demo
name: nginx-pv-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx-pv-demo
template:
metadata:
labels:
app: nginx-pv-demo
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
nfs:
server: 172.31.0.2
path: /nfs/data/nginx-pv

# 持久卷

PV:持久卷(Persistent Volume),将应用需要持久化的数据保存到指定位置

PVC:持久卷申明(Persistent Volume Claim),申明需要使用的持久卷规格

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
PersistentVolume(PV)是群集中的一块存储,由管理员配置或使用存储类动态配置。 它是集群中的资源,就像pod是k8s集群资源一样。 PV是容量插件,如Volumes,其生命周期独立于使用PV的任何单个pod。


PersistentVolumeClaim(PVC)是一个持久化存储卷,我们在创建pod时可以定义这个类型的存储卷。 它类似于一个pod。 Pod消耗节点资源,PVC消耗PV资源。 Pod可以请求特定级别的资源(CPU和内存)。 pvc在申请pv的时候也可以请求特定的大小和访问模式(例如,可以一次读写或多次只读)。

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

(1)pv的供应方式
可以通过两种方式配置PV:静态或动态。

静态的:
集群管理员创建了许多PV。它们包含可供群集用户使用的实际存储的详细信息。它们存在于Kubernetes API中,可供使用。

动态的:
当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,群集可能会尝试为PVC专门动态配置卷。此配置基于StorageClasses,PVC必须请求存储类,管理员必须创建并配置该类,以便进行动态配置。

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


(3)使用
a)需要找一个存储服务器,把它划分成多个存储空间;
b)k8s管理员可以把这些存储空间定义成多个pv;
c)在pod中使用pvc类型的存储卷之前需要先创建pvc,通过定义需要使用的pv的大小和对应的访问模式,找到合适的pv;
d)pvc被创建之后,就可以当成存储卷来使用了,我们在定义pod时就可以使用这个pvc的存储卷
e)pvc和pv它们是一一对应的关系,pv如果被pvc绑定了,就不能被其他pvc使用了;
f)我们在创建pvc的时候,应该确保和底下的pv能绑定,如果没有合适的pv,那么pvc就会处于pending状态。

(4)回收策略
当我们创建pod时如果使用pvc做为存储卷,那么它会和pv绑定,当删除pod,pvc和pv绑定就会解除,解除之后和pvc绑定的pv卷里的数据需要怎么处理,目前,卷可以保留,回收或删除:
Retain
Recycle (不推荐使用,1.15可能被废弃了)
Delete
1、Retain
当删除pvc的时候,pv仍然存在,处于released状态,但是它不能被其他pvc绑定使用,里面的数据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略

Delete
删除pvc时即会从Kubernetes中移除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
mkdir /data/volume_test/v{1,2,3,4,5,6,7,8,9,10} -p

vim /etc/exports
/data/volume_test/v1 *(rw,no_root_squash)
/data/volume_test/v2 *(rw,no_root_squash)
/data/volume_test/v3 *(rw,no_root_squash)
/data/volume_test/v4 *(rw,no_root_squash)
/data/volume_test/v5 *(rw,no_root_squash)
/data/volume_test/v6 *(rw,no_root_squash)
/data/volume_test/v7 *(rw,no_root_squash)
/data/volume_test/v8 *(rw,no_root_squash)
/data/volume_test/v9 *(rw,no_root_squash)
/data/volume_test/v10 *(rw,no_root_squash)

exportfs -arv

apiVersion: v1
kind: PersistentVolume
metadata:
name: v1
labels:
app: v1
spec:
nfs:
path: /data/volume_test/v1
server: 192.168.13.177
readOnly: false
accessModes: ["ReadWriteOnce"]
capacity:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v2
labels:
app: v2
spec:
nfs:
path: /data/volume_test/v2
server: 192.168.13.177
readOnly: false
accessModes: ["ReadOnlyMany"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: v3
labels:
app: v3
spec:
nfs:
path: /data/volume_test/v3
server: 192.168.13.177
readOnly: false
accessModes: ["ReadWriteMany"]
capacity:
storage: 3Gi
1
2
3
4
5
6
7
8
9
RWO:readwariteonce:  单路读写,允许同一个node节点上的pod访问
ROX: readonlymany: 多路只读,允许不同node节点的pod以只读方式访问
RWX: readwritemany: 多路读写,允许不同的node节点的pod以读写方式访问

[root@master1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
v1 1Gi RWO Retain Available 9m1s
v2 2Gi ROX Retain Available 9m1s
v3 3Gi RWX Retain Available 9m1s
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
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-v1
spec:
accessModes: ["ReadWriteOnce"]
selector:
matchLabels:
app: v1
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-v2
spec:
accessModes: ["ReadOnlyMany"]
selector:
matchLabels:
app: v2
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-v3
spec:
accessModes: ["ReadWriteMany"]
selector:
matchLabels:
app: v3
resources:
requests:
storage: 3Gi

[root@master1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-v1 Bound v1 1Gi RWO 3m13s
pvc-v2 Bound v2 2Gi ROX 43s
pvc-v3 Bound v3 3Gi RWX 43s
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
apiVersion: apps/v1
kind: Deployment
metadata:
name: pvc-test
spec:
replicas: 2
selector:
matchLabels:
cunchu: pvc
template:
metadata:
labels:
cunchu: pvc
spec:
containers:
- image: nginx
name: test-nfs
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- name: nginx-html
mountPath: /usr/share/nginx/html
volumes:
- persistentVolumeClaim:
claimName: pvc-v1
name: nginx-html

1、我们每次创建pvc的时候,需要事先有划分好的pv,这样可能不方便,那么可以在创建pvc的时候直接动态创建一个pv这个存储类,pv事先是不存在的
2、pvc和pv绑定,如果使用默认的回收策略retain,那么删除pvc之后,pv会处于released状态,我们想要继续使用这个pv,需要手动删除pv,kubectl delete pv pv_name,删除pv,不会删除pv里的数据,当我们重新创建pvc时还会和这个最匹配的pv绑定,数据还是原来数据,不会丢失。


删除pvc的步骤:
需要先删除使用pvc的pod
再删除pvc

回收策略为retain,删除pvc,pv状态release,需要删除pv重新创建才能为bound状态
Delete策略,删除pvc,pv状态Failed

Delete策略,删除pv后端存储所用的数据目录不会被删除,与回收策略无关

创建 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
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv01-10m
spec:
capacity:
storage: 10M
accessModes:
- ReadWriteMany
storageClassName: nfs
nfs:
path: /nfs/data/01
server: 172.31.0.2
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv02-1gi
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
storageClassName: nfs
nfs:
path: /nfs/data/02
server: 172.31.0.2
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv03-3gi
spec:
capacity:
storage: 3Gi
accessModes:
- ReadWriteMany
storageClassName: nfs
nfs:
path: /nfs/data/03
server: 172.31.0.2
1
2
3
4
5
6
[root@master ~]# kubectl get persistentvolume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 59s
pv02-1gi 1Gi RWX Retain Available nfs 59s
pv03-3gi 3Gi RWX Retain Available nfs 59s

创建 pvc

1
2
3
4
5
6
7
8
9
10
11
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 200Mi
storageClassName: nfs
1
2
3
4
5
6
7
8
9
10
11
[root@master ~]# kubectl get pv 
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 3m48s
pv02-1gi 1Gi RWX Retain Bound default/nginx-pvc nfs 3m48s
pv03-3gi 3Gi RWX Retain Available nfs 3m48s

[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nginx-pvc Bound pv02-1gi 1Gi RWX nfs 107s


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
# 创建dep时挂载
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-deploy-pvc
name: nginx-deploy-pvc
spec:
replicas: 2
selector:
matchLabels:
app: nginx-deploy-pvc
template:
metadata:
labels:
app: nginx-deploy-pvc
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: nginx-pvc

# storageclass

上面介绍的 PV 和 PVC 模式都是需要先创建好 PV,然后定义好 PVC 和 pv 进行一对一的 Bond,但是如果 PVC 请求成千上万,那么就需要创建成千上万的 PV,对于运维人员来说维护成本很高,Kubernetes 提供一种自动创建 PV 的机制,叫 StorageClass,它的作用就是创建 PV 的模板。k8s 集群管理员通过创建 storageclass 可以动态生成一个存储卷 pv 供 k8s pvc 使用。

每个 StorageClass 都包含字段 provisioner,parameters 和 reclaimPolicy。

具体来说,StorageClass 会定义以下两部分:
1、PV 的属性 ,比如存储的大小、类型等;
2、创建这种 PV 需要使用到的存储插件,比如 Ceph、NFS 等

有了这两部分信息,Kubernetes 就能够根据用户提交的 PVC,找到对应的 StorageClass,然后 Kubernetes 就会调用 StorageClass 声明的存储插件,创建出需要的 PV。

provisioner:供应商,storageclass 需要有一个供应者,用来确定我们使用什么样的存储来创建 pv,常见的 provisioner 如下

https://kubernetes.io/zh/docs/concepts/storage/storage-classes/):

provisioner 既可以由内部供应商提供,也可以由外部供应商提供,如果是外部供应商可以参考 https://github.com/kubernetes-incubator/external-storage/ 下提供的方法创建。

https://github.com/kubernetes-sigs/sig-storage-lib-external-provisioner

image-20230519162407835

以 NFS 为例,要想使用 NFS,我们需要一个 nfs-client 的自动装载程序,称之为 provisioner,这个程序会使用我们已经配置好的 NFS 服务器自动创建持久卷,也就是自动帮我们创建 PV。

reclaimPolicy:回收策略

allowVolumeExpansion:允许卷扩展,PersistentVolume 可以配置成可扩展。将此功能设置为 true 时,允许用户通过编辑相应的 PVC 对象来调整卷大小。当基础存储类的 allowVolumeExpansion 字段设置为 true 时,以下类型的卷支持卷扩展。

image-20230519162535255

注意:此功能仅用于扩容卷,不能用于缩小卷。

1
2
3
4
5
安装nfs provisioner,用于配合存储类动态生成pv
#把nfs-subdir-external-provisioner.tar.gz上传到node2和node1上,手动解压。

[root@node2 ~]# ctr -n=k8s.io images import nfs-subdir-external-provisioner.tar.gz
[root@node1 ~]# ctr -n=k8s.io images import nfs-subdir-external-provisioner.tar.gz
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
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner

对sa授权
[root@master1]# kubectl create clusterrolebinding nfs-provisioner-clusterrolebinding --clusterrole=cluster-admin --serviceaccount=default:nfs-provisioner

安装nfs-provisioner程序
[root@master1 ~]# mkdir /data/nfs_pro -p
#把/data/nfs_pro变成nfs共享的目录
[root@master1 ~]# cat /etc/exports
/data/nfs_pro 192.168.40.0/24(rw,no_root_squash)

[root@master1 ~]# exportfs -arv

kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-provisioner
spec:
selector:
matchLabels:
app: nfs-provisioner
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-provisioner
image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: example.com/nfs
- name: NFS_SERVER
value: 192.168.13.180
- name: NFS_PATH
value: /data/nfs_pro
volumes:
- name: nfs-client-root
nfs:
server: 192.168.13.180
path: /data/nfs_pro

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs
provisioner: example.com/nfs

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim1
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 1Gi
storageClassName: nfs


[root@prometheus-master storage-class]# cd /data/nfs_pro/
[root@prometheus-master nfs_pro]# ls
default-test-claim1-pvc-85419aac-02d0-4790-a2ed-3eed433841b5

1
2
3
4
步骤总结:
1、供应商:创建一个nfs provisioner
2、创建storageclass,storageclass指定刚才创建的供应商
3、创建pvc,这个pvc指定storageclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建pod挂载
kind: Pod
apiVersion: v1
metadata:
name: read-pod
spec:
containers:
- name: read-pod
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: nfs-pvc
mountPath: /usr/share/nginx/html
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim1
1
创建pvc时不需要先创建pv,只需要先创建sc,sc动态生成pv,pvc自动和pv绑定

# statefulset

StatefulSet 是为了管理有状态服务的问题而设计的

有状态服务
StatefulSet 是有状态的集合,管理有状态的服务,它所管理的 Pod 的名称不能随意变化。数据持久化的目录也是不一样,每一个 Pod 都有自己独有的数据持久化存储目录。比如 MySQL 主从、redis 集群等。
pod 删除后名字不变

无状态服务
RC、Deployment、DaemonSet 都是管理无状态的服务,它们所管理的 Pod 的 IP、名字,启停顺序等都是随机的。个体对整体无影响,所有 pod 都是共用一个数据卷的,部署的 tomcat 就是无状态的服务,tomcat 被删除,在启动一个新的 tomcat,加入到集群即可,跟 tomcat 的名字无关。

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
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:
replicas: 2
selector:
matchLabels:
app: nginx
serviceName: "nginx"
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteMany"]
storageClassName: nfs
resources:
requests:
storage: 1Gi

# 创建sts必须创建一个没有ip的service

[root@master1 ~]# kubectl get sts
NAME READY AGE
web 2/2 66s

[root@master1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-provisioner-68878cc575-zckvq 1/1 Running 0 18
web-0 1/1 Running 0 68s
web-1 1/1 Running 0 64s
# sts创建的每个pod都是独享数据目录
[root@master1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim1 Bound pvc-c23b6e3e-6e95-47b7-9fba-98e97101a541 1Gi RWX nfs 16m
www-web-0 Bound pvc-6b0fb835-f835-49cd-b6b8-8805d52af664 1Gi RWX nfs 73s
www-web-1 Bound pvc-e4001db8-3ffe-4274-86db-2386933b3b1c 1Gi RWX nfs 69s

[root@master1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON
pvc-6b0fb835-f835-49cd-b6b8-8805d52af664 1Gi RWX Delete Bound default/www-web-0 nfs
pvc-c23b6e3e-6e95-47b7-9fba-98e97101a541 1Gi RWX Delete Bound default/test-claim1 nfs
pvc-e4001db8-3ffe-4274-86db-2386933b3b1c 1Gi RWX Delete Bound default/www-web-1 nfs

使用busybox解析svc
nslookup nginx

Statefulset 总结:

1、Statefulset 管理的 pod,pod 名字是有序的,由 statefulset 的名字 - 0、1、2 这种格式组成

2、创建 statefulset 资源的时候,必须事先创建好一个 service, 如果创建的 service 没有 ip,那对这个 service 做 dns 解析,会找到它所关联的 pod ip,如果创建的 service 有 ip,那对这个 service 做 dns 解析,会解析到 service 本身 ip。

3、statefulset 管理的 pod,删除 pod,新创建的 pod 名字跟删除的 pod 名字是一样的

4、statefulset 具有 volumeclaimtemplate 这个字段,这个是卷申请模板,会自动创建 pv,pvc 也会自动生成,跟 pv 进行绑定,那如果创建的 statefulset 使用了 volumeclaimtemplate 这个字段,那创建 pod,数据目录是独享的

5、ststefulset 创建的 pod,是有域名的(域名组成:pod-name.svc-name.svc-namespace.svc.cluster.local)

# statefulset 扩缩容

1
2
3
replicas: + -
缩容删除pod顺序是由大到小,即每次删除序号最大的那个
扩容是从小到大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 滚动更新
[root@master1 ~]# kubectl explain sts.spec.updateStrategy
KIND: StatefulSet
VERSION: apps/v1

RESOURCE: updateStrategy <Object>

DESCRIPTION:
updateStrategy indicates the StatefulSetUpdateStrategy that will be
employed to update Pods in the StatefulSet when a revision is made to
Template.

StatefulSetUpdateStrategy indicates the strategy that the StatefulSet
controller will use to perform updates. It includes any additional
parameters necessary to perform the update for the indicated strategy.

FIELDS:
rollingUpdate <Object>
RollingUpdate is used to communicate parameters when Type is
RollingUpdateStatefulSetStrategyType.

type <string>
Type indicates the type of the StatefulSetUpdateStrategy. Default is
RollingUpdate.
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
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:
updateStrategy:
rollingUpdate:
partition: 1 # 先更新序号>=1的pod
maxUnavailable: 0
replicas: 2
selector:
matchLabels:
app: nginx
serviceName: "nginx"
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteMany"]
storageClassName: nfs
resources:
requests:
storage: 1Gi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
web-0                              1/1     Running   0          57s
web-1 1/1 Running 0 55s
web-1 1/1 Terminating 0 71s
web-1 1/1 Terminating 0 71s
web-1 0/1 Terminating 0 72s
web-1 0/1 Terminating 0 73s
web-1 0/1 Terminating 0 73s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 1s
web-1 0/1 ContainerCreating 0 2s
web-1 1/1 Running 0 3s

partition:1 更新的时候会把pod序号>=1的进行更新
partition:0 所有和yaml不符的pod都会更新



type: OnDelte
spec:
updateStrategy:
type: OnDelete
pod不会自动更新,需要手动删除pod,statefulset会在自动用新的yaml创建新的pod

# DaemonSet

DaemonSet 控制器能够确保 k8s 集群所有的节点都运行一个相同的 pod 副本,当向 k8s 集群中增加 node 节点时,这个 node 节点也会自动创建一个 pod 副本,当 node 节点从集群移除,这些 pod 也会自动删除;删除 Daemonset 也会删除它们创建的 pod

daemonset 的控制器会监听 kuberntes 的 daemonset 对象、pod 对象、node 对象,这些被监听的对象之变动,就会触发 syncLoop 循环让 kubernetes 集群朝着 daemonset 对象描述的状态进行演进。

在集群的每个节点上运行存储,比如:glusterd 或 ceph。
在每个节点上运行日志收集组件,比如:flunentd 、 logstash、filebeat 等。
在每个节点上运行监控组件,比如:Prometheus、 Node Exporter 、collectd 等。

Deployment 部署的副本 Pod 会分布在各个 Node 上,每个 Node 都可能运行好几个副本。
DaemonSet 的不同之处在于:每个 Node 上最多只能运行一个副本。

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
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd
template:
metadata:
labels:
name: fluentd
spec:
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varcontainers
hostPath:
path: /var/lib/docker/containers
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- image: fluentd
imagePullPolicy: IfNotPresent
name: fluentd
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
volumeMounts:
- name: varcontainers
mountPath: /var/lib/docker/containers

[root@master1 ~]# kubectl get pod -n kube-system -l name=fluentd -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
fluentd-gv4jk 1/1 Running 0 87s 10.244.136.3 master3 <none> <none>
fluentd-ttcgl 1/1 Running 0 87s 10.244.180.2 master2 <none> <none>
fluentd-w5vxw 1/1 Running 0 87s 10.244.166.146 node1 <none> <none>
fluentd-w9m2w 1/1 Running 0 87s 10.244.137.70 master1 <none> <none>
四个pod因为集群是三主一从,且定义了容忍度容忍matser污点

[root@master1 ~]# kubectl get ds -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
fluentd 4 4 4 4 4 <none> 106s

# 滚动更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spec:
updateStrategy:
rollingUpdate:
maxUnavailable: 1 # 更新期间最大只能有一个不可用,即一个一个更新


# 更新镜像 控制器名字 pod名字 镜像名字
kubectl set image daemonset fluentd fluentd=nginx -n kube-system

fluentd-55p5p 0/1 Terminating 0 14s
fluentd-55p5p 0/1 Terminating 0 14s
fluentd-55p5p 0/1 Terminating 0 14s
fluentd-55p5p 0/1 Terminating 0 14s
fluentd-qk56k 0/1 Pending 0 0s
fluentd-qk56k 0/1 Pending 0 0s
fluentd-qk56k 0/1 ContainerCreating 0 1s
fluentd-qk56k 0/1 ContainerCreating 0 3s
fluentd-qk56k 1/1 Running 0 3s

# ConfigMap

Configmap 是 k8s 中的资源对象,用于保存非机密性的配置的,数据可以用 key/value 键值对的形式保存,也可通过文件的形式保存。

我们在部署服务的时候,每个服务都有自己的配置文件,如果一台服务器上部署多个服务:nginx、tomcat、apache 等,那么这些配置都存在这个节点上,假如一台服务器不能满足线上高并发的要求,需要对服务器扩容,扩容之后的服务器还是需要部署多个服务:nginx、tomcat、apache,新增加的服务器上还是要管理这些服务的配置,如果有一个服务出现问题,需要修改配置文件,每台物理节点上的配置都需要修改,这种方式肯定满足不了线上大批量的配置变更要求。 所以,k8s 中引入了 Configmap 资源对象,可以当成 volume 挂载到 pod 中,实现统一的配置管理。

1、Configmap 是 k8s 中的资源, 相当于配置文件,可以有一个或者多个 Configmap;

2、Configmap 可以做成 Volume,k8s pod 启动之后,通过 volume 形式映射到容器内部指定目录上;

3、容器中应用程序按照原有方式读取容器特定目录上的配置文件。

4、在容器看来,配置文件就像是打包在容器内部特定目录,整个过程对应用没有任何。

应用场景:

1、使用 k8s 部署应用,当你将应用配置写进代码中,更新配置时也需要打包镜像,configmap 可以将配置信息和 docker 镜像解耦,以便实现镜像的可移植性和可复用性,因为一个 configMap 其实就是一系列配置信息的集合,可直接注入到 Pod 中给容器使用。configmap 注入方式有两种,一种将 configMap 做为存储卷,一种是将 configMap 通过 env 中 configMapKeyRef 注入到容器中。

2、使用微服务架构的话,存在多个服务共用配置的情况,如果每个服务中单独一份配置的话,那么更新配置就很麻烦,使用 configmap 可以友好的进行配置共享。

configmap 局限性:

ConfigMap 在设计上不是用来保存大量数据的。在 ConfigMap 中保存的数据不可超过 1 MiB。如果你需要保存超出此尺寸限制的数据,可以考虑挂载存储卷或者使用独立的数据库或者文件服务。

# 创建 configMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1.命令行创建
# 直接在命令行中指定configmap参数创建,通过--from-literal指定参数
kubectl create configmap tomcat-config --from-literal=tomcat_port=8080 --from-literal=server_name=myapp.tomcat.com
[root@master1 ~]# kubectl get cm
NAME DATA AGE
tomcat-config 2 6s

[root@master1 ~]# kubectl describe configmap tomcat-config
Name: tomcat-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
server_name:
----
myapp.tomcat.com
tomcat_port:
----
8080
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2.文件创建
创建nginx.conf:
server {
listen 80;
root html;
}
[root@master1 ~]# kubectl create cm www-nginx --from-file=www=./nginx.conf
configmap/www-nginx created
[root@master1 ~]# kubectl describe cm www
Name: www-nginx
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
www:
----
server {
listen 80;
root html;
}
# 不指定cm名称就是文件名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 目录创建
# 整个目录里面的内容都会变成配置文件
[root@master1 ~]# kubectl create cm mysql-config --from-file=./test/
configmap/mysql-config created
[root@master1 ~]# kubectl describe cm mysql-config
Name: mysql-config
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
my-cnf:
----
server-id=1

my-slave-cnf:
----
server-id=2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# yaml文件创建
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
master.cnf: |
[mysqld]
log-bin
log_bin_trust_function_creators=1
lower_case_table_names=1
slave.cnf: |
[mysqld]
super-read-only
log_bin_trust_function_creators=1
# | 代表配置文件有多行

# yaml 文件创建 pod 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# yaml文件创建pod中使用
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
log: "1"
lower: "2"

# 1.通过环境变量引入:使用configMapKeyRef
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: busybox
command: [ "/bin/sh", "-c", "sleep 3600" ]
env:
- name: log_bin #定义环境变量log_bin
valueFrom:
configMapKeyRef:
name: mysql #指定configmap的名字
key: log #指定configmap中的key
- name: lower #定义环境变量lower
valueFrom:
configMapKeyRef:
name: mysql
key: lower
restartPolicy: Never

[root@master1 ~]# kubectl exec -it mysql-pod -- /bin/sh
/ # printenv
log_bin=1
lower=2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.使用envfrom
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod-envfrom
spec:
containers:
- name: mysql
image: busybox
imagePullPolicy: IfNotPresent
command: [ "/bin/sh", "-c", "sleep 3600" ]
envFrom:
- configMapRef:
name: mysql #指定configmap的名字
restartPolicy: Never

[root@master1 ~]# kubectl exec -it mysql-pod-envfrom -c mysql -- /bin/sh
/ # printenv
lower=2
log=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
# 3.以存储卷方式挂载
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
log: "1"
lower: "1"
my.cnf: |
[mysqld]
haha=shabi
---
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod-volume
spec:
containers:
- name: mysql
image: busybox
imagePullPolicy: IfNotPresent
command: [ "/bin/sh","-c","sleep 3600" ]
volumeMounts:
- name: mysql-config
mountPath: /tmp/config
volumes:
- name: mysql-config
configMap:
name: mysql
restartPolicy: Never

[root@master1 ~]# kubectl exec -it mysql-pod-volume -c mysql -- /bin/sh
/ # cd /tmp/config/
/tmp/config # ls
log lower my.cnf
/tmp/config # cat my.cnf
[mysqld]
haha=shabi
# 以存储卷挂载不会在环境变量中创建key和value
1
2
configmap在以volume启动时可以热加载,cm修改,pod内配置也会自动更新,但需要一定时间
环境变量注入不会自动更新cm,即不能热加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master ~]# kubectl create cm redis-conf --from-file=redis.conf 
[root@master ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 3d16h
redis-conf 1 6s

# configmap 会保存在 etcd中
[root@master ~]# rm -rf redis.conf

[root@master ~]# kubectl get cm redis-conf -oyaml
apiVersion: v1
data:
redis.conf: |
appendonly yes
kind: ConfigMap
metadata:
name: redis-conf
namespace: default
# data是所有真正的数据
# key默认是文件名 value是配置文件内容
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
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis
command:
- redis-server
- "/redis-master/redis.conf" #指的是redis容器内部的位置
ports:
- containerPort: 6379
volumeMounts:
- mountPath: /data
name: data
- mountPath: /redis-master
name: config # config 是下面configmap的名字
volumes:
- name: data
emptyDir: {}
- name: config
configMap:
name: redis-conf # redis-conf的configmap在上面已经创建过
items: # items获取data中的内容
- key: redis.conf # 取出redis-conf对应的value
path: redis.conf # /redis-master/redis.conf
1
2
3
4
5
6
7
8
9
[root@master ~]# kubectl edit cm redis-conf 
configmap/redis-conf edited

# 外部修改配置文件,内部自动同步


redis-cli
config get appendonly
# 需要重新启动pod,因为pod的中间件没有热更新能力

# secret

Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 SSH 密钥。 将这些信息放在 secret 中比放在 Pod 的定义或者 容器镜像 中来说更加安全和灵活。

Configmap 一般是用来存放明文数据的,如配置文件,对于一些敏感数据,如密码、私钥等数据时,要用 secret 类型。

Secret 解决了密码、token、秘钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。Secret 可以以 Volume 或者环境变量的方式使用。

要使用 secret,pod 需要引用 secret。Pod 可以用两种方式使用 secret:作为 volume 中的文件被挂载到 pod 中的一个或者多个容器里,或者当 kubelet 为 pod 拉取镜像时使用。

secret 可选参数有三种:

generic: 通用类型,通常用于存储密码数据。

tls:此类型仅用于存储私钥和证书。

docker-registry: 若要保存 docker 仓库的认证信息的话,就必须使用此种类型来创建。

Secret 类型:

Service Account:用于被 serviceaccount 引用。serviceaccout 创建时 Kubernetes 会默认创建对应的 secret。Pod 如果使用了 serviceaccount,对应的 secret 会自动挂载到 Pod 的 /run/secrets/kubernetes.io/serviceaccount 目录中。

Opaque:base64 编码格式的 Secret,用来存储密码、秘钥等。可以通过 base64 --decode 解码获得原始数据,因此安全性弱

kubernetes.io/dockerconfigjson:用来存储私有 docker registry 的认证信息。

# 创建 secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@master1 secret]# kubectl create secret generic mysql-password --from-literal=password=123456
secret/mysql-password created

[root@master1 secret]# kubectl get secrets
NAME TYPE DATA AGE
mysql-password Opaque 1 11s

[root@master1 secret]# kubectl describe secrets mysql-password
Name: mysql-password
Namespace: default
Type: Opaque

Data
====
password: 6 bytes

# 使用 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
# 环境变量挂载secret
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
labels:
app: myapp
spec:
containers:
- name: myapp
image: nginx
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
env:
- name: MYSQL_ROOT_PASSWORD #它是Pod启动成功后,Pod中容器的环境变量名.
valueFrom:
secretKeyRef:
name: mysql-password #这是secret的对象名
key: password #它是secret中的key名

[root@master1 secret]# kubectl exec -it pod-secret -- /bin/sh
# printenv
MYSQL_ROOT_PASSWORD=123456


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
# 手动加密
[root@master1 secret]# echo -n "admin" | base64
YWRtaW4=
[root@master1 secret]# echo -n "YWRtaW4=" | base64 -d
admin


apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MTIzNDU2 # 用户名和密码都是base64加密

[root@master1 secret]# kubectl describe secrets mysecret
Name: mysecret
Namespace: default
Labels: <none>
Annotations: <none>

Type: Opaque

Data
====
password: 6 bytes
username: 5 bytes

# volume挂载secret
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-volume
spec:
containers:
- name: myapp
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: secret-volume
mountPath: /etc/secret
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: mysecret

[root@master1 secret]# kubectl exec -it pod-secret-volume -c myapp -- /bin/bash
root@pod-secret-volume:/# cd /etc/secret
root@pod-secret-volume:/etc/secret# ls
password username
root@pod-secret-volume:/etc/secret# cat password
123456
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@master ~]# kubectl create secret docker-registry kbshire-docker --docker-username=kbshire --docker-password=shabi --docker-email=kbshire@163.com

[root@master ~]# kubectl get secret
NAME TYPE DATA AGE
default-token-mlttj kubernetes.io/service-account-token 3 3d17h
kbshire-docker kubernetes.io/dockerconfigjson 1 43s
[root@master ~]# kubectl get secret kbshire-docker -oyaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsidXNlcm5hbWUiOiJrYnNoaXJlIiwicGFzc3dvcmQiOiIxODMxMDg5MTNhYkMiLCJlbWFpbCI6Imtic2hpcmVAMTYzLmNvbSIsImF1dGgiOiJhMkp6YUdseVpUb3hPRE14TURnNU1UTmhZa009In19fQ==
kind: Secret
metadata:
creationTimestamp: "2023-04-12T05:50:16Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:.dockerconfigjson: {}
f:type: {}
manager: kubectl-create
operation: Update
time: "2023-04-12T05:50:16Z"
name: kbshire-docker
namespace: default
resourceVersion: "621248"
uid: 84367164-813e-4f1d-8641-da19acd151b7
type: kubernetes.io/dockerconfigjson

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: private-nginx
spec:
containers:
- name: private-nginx
image: leifengyang/guignginx:v1.0
imagePullSecrets:
- name: kbshire-docker # 创建过的secret名称

# RBAC

认证

kubernetes 主要通过 APIserver 对外提供服务,那么就需要对访问 apiserver 的用户做认证,如果任何人都能访问 apiserver,那么就可以随意在 k8s 集群部署资源,这是非常危险的,也容易被黑客攻击渗透,所以需要我们对访问 k8s 系统的 apiserver 的用户进行认证,确保是合法的符合要求的用户。

授权

认证通过后仅代表它是一个被 apiserver 信任的用户,能访问 apiserver,但是用户是否拥有删除资源的权限, 需要进行授权操作,常见的授权方式有 rbac 授权。

准入控制

当用户经过认证和授权之后,最后一步就是准入控制了,k8s 提供了多种准入控制机制,它有点类似 "插件",为 apiserver 提供了很好的 "可扩展性"。请求 apiserver 时,通过认证、鉴权后、持久化 (“api 对象 "保存到 etcd) 前,会经过" 准入控制器”,让它可以做 "变更和验证"。
如果我们创建 pod 时定义了资源上下限,但不满足 LimitRange 规则中定义的资源上下限,此时 LimitRanger 就会拒绝我们创建此 pod

为什么需要准入控制器呢?
如果我们创建 pod 时定义了资源上下限,但不满足 LimitRange 规则中定义的资源上下限,此时 LimitRanger 就会拒绝我们创建此 pod

假如我们定义了一个名称空间叫做 test-aa,这个名称空间做下资源限制:限制最多可以使用 10vCPU、10Gi 内存,在这个名称空间 test-aa 下创建的所有 pod,定义 limit 的时候,所有 pod 的 limit 值不能超过 test-aa 这个名称空间设置的 limit 上线。

k8s 客户端访问 apiserver 的几种认证方式

1)客户端认证:
客户端认证也称为双向 TLS 认证, kubectl 在访问 apiserver 的时候,apiserver 也要认证 kubectl 是否是合法的,他们都会通过 ca 根证书来进行验证,如下图:

image-20230625160345722

2)Bearertoken
Bearertoken 的方式,可以理解为 apiserver 将一个密码通过了非对称加密的方式告诉了 kubectl,然后通过该密码进行相互访问,如下图:

image-20230625160451787

Kubectl 访问 k8s 集群,要找一个 kubeconfig 文件,基于 kubeconfig 文件里的用户访问 apiserver

  1. ServiceAccount

上面客户端证书认证和 Bearertoken 的两种认证方式,都是外部访问 apiserver 的时候使用的方式,那么我们这次说的 Serviceaccount 是内部访问 pod 和 apiserver 交互时候采用的一种方式。

Serviceaccount 包括了,namespace、token、ca,且通过目录挂载的方式给予 pod,当 pod 运行起来的时候,就会读取到这些信息,从而使用该方式和 apiserver 进行通信。

在 K8S 集群当中,当我们使用 kubectl 操作 k8s 资源时候,需要确定我们用哪个用户访问哪个 k8s 集群,kubectl 操作 k8s 集群资源会去 /root/.kube 目录下找 config 文件,可以通过 kubectl config 查看 config 文件配置,如下:

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@master1 ~]# kubectl config view 
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.13.249:16443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

# 指定使用用户访问集群
kubectl get pods --kubeconfig=/root/.kube/config

# 授权

用户通过认证之后,什么权限都没有,需要一些后续的授权操作,如对资源的增删该查等,kubernetes1.6 之后开始有 RBAC(基于角色的访问控制机制)授权检查机制。

Kubernetes 的授权是基于插件形成的,其常用的授权插件有以下几种:

1)Node(节点认证)

2)ABAC (基于属性的访问控制)

3)RBAC(基于角色的访问控制)

4)Webhook(基于 http 回调机制的访问控制)

什么是 RBAC(基于角色的授权)

让一个用户(Users)扮演一个角色(Role),角色拥有权限,从而让用户拥有这样的权限,随后在授权机制当中,只需要将权限授予某个角色,此时用户将获取对应角色的权限,从而实现角色的访问控制。

另外,k8s 为此还有一种集群级别的授权机制,就是定义一个集群角色(ClusterRole),对集群内的所有资源都有可操作的权限,从而将 User2 通过 ClusterRoleBinding 到 ClusterRole,从而使 User2 拥有集群的操作权限。

Role、RoleBinding、ClusterRole 和 ClusterRoleBinding 的关系如下:

1. 用户基于 rolebinding 绑定到 role
限定在 rolebinding 所在的名称空间

image-20230522152729791

2. 用户基于 rolebinding 绑定到 clusterrole
RoleBinding 仅仅对当前名称空间有对应的权限

image-20230522153654517

1
2
3
4
5
(1)用户通过rolebinding绑定role
(2)用户通过rolebinding绑定clusterrole

rolebinding绑定clusterrole的好处:
假如有6个名称空间,每个名称空间的用户都需要对自己的名称空间有管理员权限,那么需要定义6个role和rolebinding,然后依次绑定,如果名称空间更多,我们需要定义更多的role,这个是很麻烦的,所以我们引入clusterrole,定义一个clusterrole,对clusterrole授予所有权限,然后用户通过rolebinding绑定到clusterrole,就会拥有自己名称空间的管理员权限了

3、用户基于 clusterrolebinding 绑定到 clusterrole
不限定名称空间

image-20230522154527804

用户基于 rbac 授权有几种方案:

基于 rolebinding 绑定到 role 上

基于 rolebinding 绑定到 clusterrole 上

基于 clusterrolebinding 绑定到 clusterrole 上

# 准入控制

在 k8s 上准入控制器的模块有很多,其中比较常用的有 LimitRanger、ResourceQuota、ServiceAccount、PodSecurityPolicy (k8s1.25 废弃了) 等等,对于前面三种准入控制器系统默认是启用的,我们只需要定义对应的规则即可;对于 PodSecurityPolicy (k8s1.25 废弃了) 这种准入控制器,系统默认没有启用,如果我们要使用,就必需启用以后,对应规则才会正常生效;这里需要注意一点,对应 psp 准入控制器,一定要先写好对应的规则,把规则和权限绑定好以后,在启用对应的准入控制器,否则先启用准入控制器,没有对应的规则,默认情况它是拒绝操作,这可能导致现有的 k8s 系统跑的系统级 pod 无法正常工作;所以对于 psp 准入控制器要慎用,如果规则和权限做的足够精细,它会给我们的 k8s 系统安全带来大幅度的提升,反之,可能导致整个 k8s 系统不可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat /etc/kubernetes/manifests/kube-apiserver.yaml 
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.40.63:6443
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=192.168.40.180

- --enable-admission-plugins=NodeRestriction # 开启准入插件,逗号分隔

LimitRanger、ResourceQuota、ServiceAccount 是自动开启的

apiserver 启用准入控制插件需要使用–enable-admission-plugins 选项来指定,该选项可以使用多个值,用逗号隔开表示启用指定的准入控制插件;这里配置文件中显式启用了 NodeRestrication 这个插件;默认没有写在这上面的内置准入控制器,它也是启用了的,比如 LimitRanger、ResourceQuota、ServiceAccount 等等;对于不同的 k8s 版本,内置的准入控制器和启用与否请查看相关版本的官方文档;对于那些没有启动的准入控制器,我们可以在上面选项中直接启用,分别用逗号隔开即可。

https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/

# useraccount 、serviceaccount

kubernetes 中账户分为:UserAccounts(用户账户) 和 ServiceAccounts(服务账户) 两种:
UserAccount 是给 kubernetes 集群外部用户使用的,如 kubectl 访问 k8s 集群要用 useraccount 用户, kubeadm 安装的 k8s,默认的 useraccount 用户是 kubernetes-admin;

ServiceAccount 是 Pod 使用的账号,Pod 容器的进程需要访问 API Server 时用的就是 ServiceAccount 账户;ServiceAccount 仅局限它所在的 namespace,每个 namespace 创建时都会自动创建一个 default service account;创建 Pod 时,如果没有指定 Service Account,Pod 则会使用 default Service Account。

ua 是 kubectl 访问集群的
sa 是 pod 访问 api-server

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
# kubectl create sa sa-test

apiVersion: v1
kind: Pod
metadata:
name: sa-test
namespace: default
labels:
app: sa
spec:
serviceAccountName: sa-test
containers:
- name: sa-tomcat
ports:
- containerPort: 80
image: nginx
imagePullPolicy: IfNotPresent



[root@master1 rbac]# kubectl exec -it sa-test -- /bin/bash
root@sa-test:/# cd /var/run/secrets/kubernetes.io/serviceaccount/
root@sa-test:/var/run/secrets/kubernetes.io/serviceaccount# ls
ca.crt namespace token

root@sa-test:/var/run/secrets/kubernetes.io/serviceaccount# curl --cacert ./ca.crt -H "Authorization: Bearer $(cat ./token)" https://kubernetes/api/v1/namespaces/kube-system
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "namespaces \"kube-system\" is forbidden: User \"system:serviceaccount:default:sa-test\" cannot get resource \"namespaces\" in API group \"\" in the namespace \"kube-system\"",
"reason": "Forbidden",
"details": {
"name": "kube-system",
"kind": "namespaces"
},
"code": 403 # 因为SA没有被授权
}

# 对sa授权
kubectl create clusterrolebinding sa-test-admin --clusterrole=cluster-admin --serviceaccount=default:sa-test

root@sa-test:/var/run/secrets/kubernetes.io/serviceaccount# curl --cacert ./ca.crt -H "Authorization: Bearer $(cat ./token)" https://kubernetes/api/v1/namespaces/kube-system
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "kube-system",
"uid": "b25c9a07-c34a-4ce7-8afe-a7b3b1fa6895",
"resourceVersion": "4",
"creationTimestamp": "2023-04-25T08:50:07Z",
"managedFields": [
{
"manager": "kube-apiserver",
"operation": "Update",
"apiVersion": "v1",
"time": "2023-04-25T08:50:07Z",
"fieldsType": "FieldsV1",
"fieldsV1": {"f:status":{"f:phase":{}}}
}
]
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
/api/v1/pods # 操作pod

# rbac

在 Kubernetes 中,所有资源对象都是通过 API 进行操作,他们保存在 etcd 里。而对 etcd 的操作我们需要通过访问 kube-apiserver 来实现,上面的 Service Account 其实就是 APIServer 的认证过程,而授权的机制是通过 RBAC:基于角色的访问控制实现。

RBAC 有四个资源对象,分别是 Role、ClusterRole、RoleBinding、ClusterRoleBinding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
role:
一组权限的集合,在一个命名空间中,可以用其来定义一个角色,只能对命名空间内的资源进行授权。如果是集群级别的资源,则需要使用ClusterRole。
例如:定义一个角色用来读取Pod的权限

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: rbac
  name: pod-read
rules:
- apiGroups: [""]
  resources: ["pods"]
  resourceNames: []
verbs: ["get","watch","list"]


rules中的参数说明:
1、apiGroups:支持的API组列表,例如:"apiVersion: apps/v1"等
2、resources:支持的资源对象列表,例如pods、deployments、jobs等
3、resourceNames: 指定resource的名称
4、verbs:对资源对象的操作方法列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ClusterRole:集群角色

具有和角色一致的命名空间资源的管理能力,还可用于以下特殊元素的授权
1、集群范围的资源,例如Node
2、非资源型的路径,例如:/healthz
3、包含全部命名空间的资源,例如Pods

例如:定义一个集群角色可让用户访问任意secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: secrets-clusterrole
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: [] # 可以指定对哪个secret做授权,不写对所有做授权
verbs: ["get","watch","list"]
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
RoleBinding:角色绑定、ClusterRolebinding:集群角色绑定

角色绑定和集群角色绑定用于把一个角色绑定在一个目标上,可以是User,Group,Service Account,使用RoleBinding为某个命名空间授权,使用ClusterRoleBinding为集群范围内授权。
例如:将在rbac命名空间中把pod-read角色授予用户es
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-read-bind
namespace: rbac
subjects:
- kind: User
name: es
apiGroup: rbac.authorization.k8s.io
roleRef:
- kind: Role
name: pod-read
apiGroup: rbac.authorizatioin.k8s.io

RoleBinding也可以引用ClusterRole,对属于同一命名空间内的ClusterRole定义的资源主体进行授权, 例如:es能获取到集群中所有的资源信息
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: es-allresource
namespace: rbac
subjects:
- kind: User
name: es
apiGroup: rbac.authorization.k8s.io
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin

# 资源引用方式

多数资源可以用其名称的字符串表示,也就是 Endpoint 中的 URL 相对路径,例如 pod 中的日志是 GET /api/v1/namaspaces/{namespace}/pods/{podname}/log

如果需要在一个 RBAC 对象中体现上下级资源,就需要使用 “/” 分割资源和下级资源。

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
例如:若想授权让某个主体同时能够读取Pod和Pod log,则可以配置 resources为一个数组
[root@ ~]# kubectl create ns test

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: logs-reader
namespace: test
rules:
- apiGroups: [""]
resources: ["pods","pods/log"]
verbs: ["get","list","watch"]

[root@master1 rbac]# kubectl get role -n test
NAME CREATED AT
logs-reader 2023-05-23T06:33:45Z

[root@master1 rbac]# kubectl create sa sa-test -n test
serviceaccount/sa-test created
[root@master1 rbac]# kubectl create rolebinding sa-test-1 -n test --role=logs-reader --serviceaccount=test:sa-test
rolebinding.rbac.authorization.k8s.io/sa-test-1 created


apiVersion: v1
kind: Pod
metadata:
name: sa-test-pod
namespace: test
labels:
app: sa
spec:
serviceAccountName: sa-test
containers:
- name: sa-tomcat
ports:
- containerPort: 80
image: nginx
imagePullPolicy: IfNotPresent

[root@master1 rbac]# kubectl exec -it -n test sa-test-pod -- /bin/bash
root@sa-test-pod:/# cd /var/run/secrets/kubernetes.io/serviceaccount/
root@sa-test-pod:/var/run/secrets/kubernetes.io/serviceaccount# ls
ca.crt namespace token
root@sa-test-pod:/var/run/secrets/kubernetes.io/serviceaccount# curl --cacert ./ca.crt -H "Authorization: Bearer $(cat ./token)" https://kubernetes.default/api/v1/namespaces/test/pods/sa-test-pod/logs
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {

},
"status": "Failure",
"message": "pods \"sa-test-pod\" is forbidden: User \"system:serviceaccount:test:sa-test\" cannot get resource \"pods/logs\" in API group \"\" in the namespace \"test\"",
"reason": "Forbidden",
"details": {
"name": "sa-test-pod",
"kind": "pods"
},
"code": 403
}

root@sa-test-pod:/var/run/secrets/kubernetes.io/serviceaccount# curl --cacert ./ca.crt -H "Authorization: Bearer $(cat ./token)" https://kuberntes.default/api/v1/namespaces/test/pods/sa-test-pod/log
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/05/23 06:37:45 [notice] 1#1: using the "epoll" event method
2023/05/23 06:37:45 [notice] 1#1: nginx/1.21.5
2023/05/23 06:37:45 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2023/05/23 06:37:45 [notice] 1#1: OS: Linux 3.10.0-1160.el7.x86_64
2023/05/23 06:37:45 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2023/05/23 06:37:45 [notice] 1#1: start worker processes
2023/05/23 06:37:45 [notice] 1#1: start worker process 32
2023/05/23 06:37:45 [notice] 1#1: start worker process 33
2023/05/23 06:37:45 [notice] 1#1: start worker process 34
2023/05/23 06:37:45 [notice] 1#1: start worker process 35

资源还可以通过名称(ResourceName)进行引用,在指定ResourceName后,使用get、delete、update、patch请求,就会被限制在这个资源实例范围内

例如,下面的声明让一个主体只能对名为my-configmap的Configmap进行get和update操作:
apiVersion: rabc.authorization.k8s.io/v1
kind: Role
metadata:
namaspace: default
name: configmap-update
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["my-configmap"]
verbs: ["get","update"]

# 常见角色实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
(1)允许读取核心API组的Pod资源
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
(2)允许读写apps API组中的deployment资源
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get","list","watch","create","update","patch","delete"]
(3)允许读取Pod以及读写job信息
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["jobs"]
verbs: ["get","list","watch","create","update","patch","delete"]
(4)允许读取一个名为my-config的ConfigMap(必须绑定到一个RoleBinding来限制到一个Namespace下的ConfigMap):
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["my-configmap"]
verbs: ["get"]
(5)读取核心组的Node资源(Node属于集群级的资源,所以必须存在于ClusterRole中,并使用ClusterRoleBinding进行绑定):
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get","list","watch"]
(6)允许对非资源端点“/healthz”及其所有子路径进行GET和POST操作(必须使用ClusterRole和ClusterRoleBinding):
rules:
- nonResourceURLs: ["/healthz","/healthz/*"]
verbs: ["get","post"]

# 常见的角色绑定示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(1)用户名alice	
subjects:
- kind: User
name: alice
apiGroup: rbac.authorization.k8s.io
  
(2)组名alice
subjects:
- kind: Group
name: alice
apiGroup: rbac.authorization.k8s.io

(3)kube-system命名空间中默认Service Account
subjects:
- kind: ServiceAccount
name: default
namespace: kube-system

# SA 授权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Service Account也是一种账号,是给运行在Pod里的进程提供了必要的身份证明。需要在Pod定义中指明引用的Service Account,这样就可以对Pod的进行赋权操作。
例如:pod内可获取rbac命名空间的所有Pod资源,pod-reader-sc的Service Account是绑定了名为pod-read的Role

[root@]# kubectl create ns rbac
[root@]# kubectl create sa pod-reader-sc -n rbac
[root@]#cat pod-rbac.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: rbac
spec:
serviceAccountName: pod-reader-sc
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(1)my-namespace中的my-sa Service Account授予只读权限
kubectl create rolebinding my-sa-view --clusterrole=view --serviceaccount=my-namespace:my-sa --namespace=my-namespace

(2)为一个命名空间中名为default的Service Account授权
如果一个应用没有指定 serviceAccountName,则会使用名为default的Service Account。注意,赋予Service Account “default”的权限会让所有没有指定serviceAccountName的Pod都具有这些权限

(3)为命名空间中所有Service Account都授予一个角色
如果希望在一个命名空间中,任何Service Account应用都具有一个角色,则可以为这一命名空间的Service Account群组进行授权

kubectl create rolebinding serviceaccounts-view --clusterrole=view --group=system:serviceaccounts:my-namespace --namespace=my-namespace

(4)为集群范围内所有Service Account都授予一个低权限角色
如果不想为每个命名空间管理授权,则可以把一个集群级别的角色赋给所有Service Account。

kubectl create clusterrolebinding serviceaccounts-view --clusterrole=view --group=system:serviceaccounts

(5)为所有Service Account授予超级用户权限
kubectl create clusterrolebinding serviceaccounts-view --clusterrole=cluster-admin --group=system:serviceaccounts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# kubectl创建资源对象
(1)在命名空间rbac中为用户es授权admin ClusterRole:

kubectl create rolebinding bob-admin-binding --clusterrole=admin --user=es --namespace=rbac

  
(2)在命名空间rbac中为名为myapp的Service Account授予view ClusterRole:

kubctl create rolebinding myapp-role-binding --clusterrole=view --serviceaccount=rbac:myapp --namespace=rbac

  
(3)在全集群范围内为用户root授予cluster-admin ClusterRole:

kubectl create clusterrolebinding cluster-binding --clusterrole=cluster-admin --user=root

  
(4)在全集群范围内为名为myapp的Service Account授予view ClusterRole:

kubectl create clusterrolebinding service-account-binding --clusterrole=view --serviceaccount=myapp

# 限制不同用户访问 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
(1)生成一个私钥
cd /etc/kubernetes/pki/
(umask 077; openssl genrsa -out lucky.key 2048)
(2)生成一个证书请求
openssl req -new -key lucky.key -out lucky.csr -subj "/CN=lucky"
(3)生成一个证书
openssl x509 -req -in lucky.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out lucky.crt -days 3650
在kubeconfig下新增加一个lucky这个用户
(1)把lucky这个用户添加到kubernetes集群中,可以用来认证apiserver的连接
[root@xianchaomaster1 pki]# kubectl config set-credentials lucky --client-certificate=./lucky.crt --client-key=./lucky.key --embed-certs=true
(2)在kubeconfig下新增加一个lucky这个账号
kubectl config set-context lucky@kubernetes --cluster=kubernetes --user=lucky

kubectl config view
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
- context:
cluster: kubernetes
user: lucky
name: lucky@kubernetes
current-context: lucky@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- name: lucky
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

[root@master1 pki]# kubectl create ns lucky-ns
[root@master1 pki]# kubectl create rolebinding lucky -n lucky-test --clusterrole=cluster-admin --user=lucky

[root@master1 pki]# kubectl get pod -n lucky-test
No resources found in lucky-test namespace.
[root@master1 pki]# kubectl get pod -n default
Error from server (Forbidden): pods is forbidden: User "lucky" cannot list resource "pods" in API group "" in the namespace "default"


添加一个test的普通用户
useradd test
cp -ar /root/.kube /tmp/
修改/tmp/.kube/config文件,把kubernetes-admin相关的删除,只留lucky用户
cp -ar /tmp/.kube/ /home/test/
chown -R test.test /home/test/
su - test
kubectl get pods -n lucky

test只能使用lucky的权限访问kubernetes

退出test用户,需要在把集群环境切换成管理员权限

kubectl config use-context kubernetes-admin@kubernetes

# 授权用户能查看所有名称空间 pod 权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
ssl认证
生成一个证书
(1)生成一个私钥
cd /etc/kubernetes/pki/
(umask 077; openssl genrsa -out lucky1.key 2048)
(2)生成一个证书请求
openssl req -new -key lucky1.key -out lucky1.csr -subj "/CN=lucky66"
(3)生成一个证书
openssl x509 -req -in lucky1.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out lucky1.crt -days 3650
在kubeconfig下新增加一个lucky这个用户
(1)把lucky这个用户添加到kubernetes集群中,可以用来认证apiserver的连接
[root@master1 pki]# kubectl config set-credentials lucky66 --client-certificate=./lucky1.crt --client-key=./lucky1.key --embed-certs=true
(2)在kubeconfig下新增加一个lucky这个账号
kubectl config set-context lucky66@kubernetes --cluster=kubernetes --user=lucky66
(3)创建一个clusterrole
[root@master1 sa]# vim lucky66-clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: lucky66-get-pod
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]

[root@master1]# kubectl apply -f lucky66-clusterrole.yaml
(4)创建一个clusterrolebinding
[root@master1]# kubectl create clusterrolebinding lucky66-get-pods --clusterrole=lucky66-get-pod --user=lucky66

添加一个test66的普通用户
useradd test66
rm -rf /tmp/.kube
cp -ar /root/.kube /tmp/
修改/tmp/.kube/config文件,把kubernetes-admin和lucky相关的删除,只留lucky66用户
cp -ar /tmp/.kube/ /home/test66/
chown -R test66.test66 /home/test66/
[root@master1]# vim /home/test66/.kube/config
把current-context变成如下:
current-context: lucky66@kubernetes
su - test66
kubectl get pods
kubectl get pods -n kube-system

kubectl get ns

# ResourceQuota 准入控制器

ResourceQuota 准入控制器是 k8s 上内置的准入控制器,默认该控制器是启用的状态,它主要作用是用来限制一个名称空间下的资源的使用,它能防止在一个名称空间下的 pod 被过多创建时,导致过多占用 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
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-test
namespace: quota
spec:
hard:
pods: "6" # 数量不能超过6个
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 10Gi
count/deployments.apps: "6"
persistentvolumeclaims: "6"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: quota
namespace: quota
spec:
replicas: 2
template:
metadata:
labels:
app: quota
version: v1
spec:
containers:
- name: myapp
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
requests:
cpu: 1
memory: 1Gi
limits:
cpu: 2
memory: 2Gi
selector:
matchLabels:
app: quota
version: v1

[root@master1 rbac]# kubectl get pod -n quota # 注意限制pod数量的不只有pod数量,还有cpu和memory大小,只要有一个不满足要去就不会创建
NAME READY STATUS RESTARTS AGE
quota-8b9bc78cd-d2jcc 1/1 Running 0 16s
quota-8b9bc78cd-dzxp8 1/1 Running 0 16s
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
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-test
namespace: quota
spec:
hard:
pods: "6" # 数量不能超过6个
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 10Gi
count/deployments.apps: "6"
persistentvolumeclaims: "6"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: quota
namespace: quota
spec:
strategy:
type: Recreate
replicas: 7
template:
metadata:
labels:
app: quota
version: v1
spec:
containers:
- name: myapp
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 1
memory: 1Gi
selector:
matchLabels:
app: quota
version: v1

[root@master1 rbac]# kubectl get pod -n quota
NAME READY STATUS RESTARTS AGE
quota-646fb6b848-c2h8z 1/1 Running 0 24s
quota-646fb6b848-v5pmr 1/1 Running 0 24s
quota-646fb6b848-z2p5k 1/1 Running 0 24s
quota-646fb6b848-zwwd7 1/1 Running 0 24s
1
2
3
4
5
6
7
8
9
10
11
12
2、限制存储空间大小
apiVersion: v1
kind: ResourceQuota
metadata:
name: quota-storage-test
namespace: quota
spec:
hard:
requests.storage: "5Gi"
persistentvolumeclaims: "5"
requests.ephemeral-storage: "1Gi"
limits.ephemeral-storage: "2Gi"

requests.storage 用来限制对应名称空间下的存储下限总和,persistenvolumeclaims 用来限制 pvc 总数量,requests.ephemeral-storage 用来现在使用本地临时存储的下限总容量;limits.ephemeral-storage 用来限制使用本地临时存储上限总容量;以上配置表示在 default 名称空间下非停止状态的容器存储下限总容量不能超过 5G,pvc 的数量不能超过 5 个,本地临时存储下限容量不能超过 1G,上限不能超过 2G。

# LimitRanger 准入控制器

LimitRanger 准入控制器是 k8s 上一个内置的准入控制器,LimitRange 是 k8s 上的一个标准资源,它主要用来定义在某个名称空间下限制 pod 或 pod 里的容器对 k8s 上的 cpu 和内存资源使用;它能够定义我们在某个名称空间下创建 pod 时使用的 cpu 和内存的上限和下限以及默认 cpu、内存的上下限。
如果我们创建 pod 时定义了资源上下限,但不满足 LimitRange 规则中定义的资源上下限,此时 LimitRanger 就会拒绝我们创建此 pod;如果我们在 LimitRange 规则中定义了默认的资源上下限制,我们创建资源没有指定其资源限制,它默认会使用 LimitRange 规则中的默认资源限制;同样的逻辑 LimitRanger 可以限制一个 pod 使用资源的上下限,它还可以限制 pod 中的容器的资源上下限,比限制 pod 更加精准;不管是针对 pod 还是 pod 里的容器,它始终只是限制单个 pod 资源使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
apiVersion: v1
kind: Namespace
metadata:
name: limit
---
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-memory
namespace: limit
spec:
limits:
- default:
cpu: 1000m
memory: 1000Mi
defaultRequest:
cpu: 500m
memory: 500Mi
min:
cpu: 500m
memory: 500Mi
max:
cpu: 2000m
memory: 2000Mi
maxLimitRequestRatio:
cpu: 4
memory: 4
type: Container
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: limit #ClusterRoleBinding的名字
subjects:
- kind: ServiceAccount
name: default #serviceaccount资源对象的name
namespace: limit #serviceaccount的namespace
roleRef:
kind: ClusterRole
name: cluster-admin #k8s集群中最高权限的角色
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: default # ServiceAccount的名字
namespace: limit # serviceaccount的namespace
labels:
app: default #ServiceAccount的标签


apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-demo
namespace: limit
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx

kubectl describe pod
Limits:
cpu: 1
memory: 1000Mi
Requests:
cpu: 500m
memory: 500Mi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: pod-request
namespace: limit
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
resources:
requests:
cpu: 100m
[root@master1 rbac]# kubectl apply -f haha.yaml
Error from server (Forbidden): error when creating "haha.yaml": pods "pod-request" is forbidden: [minimum cpu usage per Container is 500m, but request is 100m, cpu max limit to request ratio per Container is 4, but provided ratio is 10.000000]

# kubesphere

建议配置: master:4c8g node:4c16g

# 平台安装

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
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

Alternatively, if you are the root user, you can run:

export KUBECONFIG=/etc/kubernetes/admin.conf

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/

You can now join any number of control-plane nodes by copying certificate authorities
and service account keys on each node and then running the following as root:

kubeadm join k8s-master:6443 --token butsu9.lcap4rmxigogjm3g \
--discovery-token-ca-cert-hash sha256:96ec447684310e31f9890dab205123d2081c5e1acbf025e88c727a1951fe0f54 \
--control-plane

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

kubeadm join k8s-master:6443 --token butsu9.lcap4rmxigogjm3g \
--discovery-token-ca-cert-hash sha256:96ec447684310e31f9890dab205123d2081c5e1acbf025e88c727a1951fe0f54

# 配置前置存储类

安装 nfs-server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 在每个机器。
yum install -y nfs-utils


# 在master 执行以下命令
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports


# 执行以下命令,启动 nfs 服务;创建共享目录
mkdir -p /nfs/data


# 在master执行
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server

# 使配置生效
exportfs -r


#检查配置是否生效
exportfs

配置 nfs-client

1
2
3
4
5
showmount -e 172.23.142.216

mkdir -p /nfs/data

mount -t nfs 172.23.142.216:/nfs/data /nfs/data

配置默认存储

配置动态供应的默认存储类

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
## 创建了一个存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "true" ## 删除pv的时候,pv的内容是否要备份

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nfs-subdir-external-provisioner:v4.0.2
# resources:
# limits:
# cpu: 10m
# requests:
# cpu: 10m
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 172.23.142.216 ## 指定自己nfs服务器地址
- name: NFS_PATH
value: /nfs/data ## nfs服务器共享的目录
volumes:
- name: nfs-client-root
nfs:
server: 172.23.142.216
path: /nfs/data
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@master ~]# kubectl apply -f sc.yml 
storageclass.storage.k8s.io/nfs-storage created
deployment.apps/nfs-client-provisioner created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created

[root@master ~]# kubectl get sc # storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storage (default) k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 18s

1
2
3
4
5
6
7
8
9
10
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 200Mi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@master ~]# kubectl apply -f pvc.yml 
persistentvolumeclaim/nginx-pvc created

[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nginx-pvc Bound pvc-2f0e7123-3311-4045-bba9-acdcbad0a572 200Mi RWX nfs-storage 10s

[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 22h
pv02-1gi 1Gi RWX Retain Released default/nginx-pvc nfs 22h
pv03-3gi 3Gi RWX Retain Available nfs 22h
pvc-2f0e7123-3311-4045-bba9-acdcbad0a572 200Mi RWX Delete Bound default/nginx-pvc nfs-storage 24s

# 安装 metrics-server

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
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
name: system:aggregated-metrics-reader
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
rules:
- apiGroups:
- ""
resources:
- pods
- nodes
- nodes/stats
- namespaces
- configmaps
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server:system:auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:metrics-server
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 0
template:
metadata:
labels:
k8s-app: metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --kubelet-insecure-tls
- --secure-port=4443
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/metrics-server:v0.4.3
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 4443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
periodSeconds: 10
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
volumes:
- emptyDir: {}
name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@master ~]# kubectl apply -f metrics 

[root@master ~]# kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
master 258m 12% 1889Mi 49%
node1 <unknown> <unknown> <unknown> <unknown>
node2 <unknown> <unknown> <unknown> <unknown>

[root@master ~]# kubectl top pods -A
NAMESPACE NAME CPU(cores) MEMORY(bytes)
default nfs-client-provisioner-786945db78-788mx 1m 11Mi
kube-system calico-kube-controllers-57c7b58f66-jq7lm 3m 23Mi
kube-system calico-node-4bttn 38m 150Mi
kube-system coredns-5897cd56c4-2fhkm 2m 14Mi
kube-system coredns-5897cd56c4-2l59f 3m 10Mi
kube-system etcd-master 13m 49Mi
kube-system kube-apiserver-master 54m 467Mi
kube-system kube-controller-manager-master 22m 58Mi
kube-system kube-proxy-d4sgz 1m 14Mi
kube-system kube-scheduler-master 3m 23Mi
kube-system metrics-server-6497cc6c5f-n4l62 3m 13Mi
tigera-operator tigera-operator-57cb64cf85-kqsvh 3m 27Mi

# 全功能安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
wget https://github.com/kubesphere/ks-installer/releases/download/v3.3.2/kubesphere-installer.yaml



wget https://github.com/kubesphere/ks-installer/releases/download/v3.3.2/cluster-configuration.yaml


[root@master ~]# vi cluster-configuration.yaml
[root@master ~]# vim cluster-configuration.yaml
[root@master ~]# kubectl apply -f kubesphere-installer.yaml
customresourcedefinition.apiextensions.k8s.io/clusterconfigurations.installer.kubesphere.io created
namespace/kubesphere-system created
serviceaccount/ks-installer created
clusterrole.rbac.authorization.k8s.io/ks-installer created
clusterrolebinding.rbac.authorization.k8s.io/ks-installer created
deployment.apps/ks-installer created
[root@master ~]# kubectl apply -f cluster-configuration.yaml
clusterconfiguration.installer.kubesphere.io/ks-installer created

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
# 修改clust.yml
---
apiVersion: installer.kubesphere.io/v1alpha1
kind: ClusterConfiguration
metadata:
name: ks-installer
namespace: kubesphere-system
labels:
version: v3.1.1
spec:
persistence:
storageClass: "" # If there is no default StorageClass in your cluster, you need to specify an existing StorageClass here.
authentication:
jwtSecret: "" # Keep the jwtSecret consistent with the Host Cluster. Retrieve the jwtSecret by executing "kubectl -n kubesphere-system get cm kubesphere-config -o yaml | grep -v "apiVersion" | grep jwtSecret" on the Host Cluster.
local_registry: "" # Add your private registry address if it is needed.
etcd:
monitoring: true # Enable or disable etcd monitoring dashboard installation. You have to create a Secret for etcd before you enable it.
endpointIps: 172.31.0.4 # etcd cluster EndpointIps. It can be a bunch of IPs here.
port: 2379 # etcd port.
tlsEnable: true
common:
redis:
enabled: true
openldap:
enabled: true
minioVolumeSize: 20Gi # Minio PVC size.
openldapVolumeSize: 2Gi # openldap PVC size.
redisVolumSize: 2Gi # Redis PVC size.
monitoring:
# type: external # Whether to specify the external prometheus stack, and need to modify the endpoint at the next line.
endpoint: http://prometheus-operated.kubesphere-monitoring-system.svc:9090 # Prometheus endpoint to get metrics data.
es: # Storage backend for logging, events and auditing.
# elasticsearchMasterReplicas: 1 # The total number of master nodes. Even numbers are not allowed.
# elasticsearchDataReplicas: 1 # The total number of data nodes.
elasticsearchMasterVolumeSize: 4Gi # The volume size of Elasticsearch master nodes.
elasticsearchDataVolumeSize: 20Gi # The volume size of Elasticsearch data nodes.
logMaxAge: 7 # Log retention time in built-in Elasticsearch. It is 7 days by default.
elkPrefix: logstash # The string making up index names. The index name will be formatted as ks-<elk_prefix>-log.
basicAuth:
enabled: false
username: ""
password: ""
externalElasticsearchUrl: ""
externalElasticsearchPort: ""
console:
enableMultiLogin: true # Enable or disable simultaneous logins. It allows different users to log in with the same account at the same time.
port: 30880
alerting: # (CPU: 0.1 Core, Memory: 100 MiB) It enables users to customize alerting policies to send messages to receivers in time with different time intervals and alerting levels to choose from.
enabled: true # Enable or disable the KubeSphere Alerting System.
# thanosruler:
# replicas: 1
# resources: {}
auditing: # Provide a security-relevant chronological set of records,recording the sequence of activities happening on the platform, initiated by different tenants.
enabled: true # Enable or disable the KubeSphere Auditing Log System.
devops: # (CPU: 0.47 Core, Memory: 8.6 G) Provide an out-of-the-box CI/CD system based on Jenkins, and automated workflow tools including Source-to-Image & Binary-to-Image.
enabled: true # Enable or disable the KubeSphere DevOps System.
jenkinsMemoryLim: 2Gi # Jenkins memory limit.
jenkinsMemoryReq: 1500Mi # Jenkins memory request.
jenkinsVolumeSize: 8Gi # Jenkins volume size.
jenkinsJavaOpts_Xms: 512m # The following three fields are JVM parameters.
jenkinsJavaOpts_Xmx: 512m
jenkinsJavaOpts_MaxRAM: 2g
events: # Provide a graphical web console for Kubernetes Events exporting, filtering and alerting in multi-tenant Kubernetes clusters.
enabled: true # Enable or disable the KubeSphere Events System.
ruler:
enabled: true
replicas: 2
logging: # (CPU: 57 m, Memory: 2.76 G) Flexible logging functions are provided for log query, collection and management in a unified console. Additional log collectors can be added, such as Elasticsearch, Kafka and Fluentd.
enabled: true # Enable or disable the KubeSphere Logging System.
logsidecar:
enabled: true
replicas: 2
metrics_server: # (CPU: 56 m, Memory: 44.35 MiB) It enables HPA (Horizontal Pod Autoscaler).
enabled: false # Enable or disable metrics-server.
monitoring:
storageClass: "" # If there is an independent StorageClass you need for Prometheus, you can specify it here. The default StorageClass is used by default.
# prometheusReplicas: 1 # Prometheus replicas are responsible for monitoring different segments of data source and providing high availability.
prometheusMemoryRequest: 400Mi # Prometheus request memory.
prometheusVolumeSize: 20Gi # Prometheus PVC size.
# alertmanagerReplicas: 1 # AlertManager Replicas.
multicluster:
clusterRole: none # host | member | none # You can install a solo cluster, or specify it as the Host or Member Cluster.
network:
networkpolicy: # Network policies allow network isolation within the same cluster, which means firewalls can be set up between certain instances (Pods).
# Make sure that the CNI network plugin used by the cluster supports NetworkPolicy. There are a number of CNI network plugins that support NetworkPolicy, including Calico, Cilium, Kube-router, Romana and Weave Net.
enabled: true # Enable or disable network policies.
ippool: # Use Pod IP Pools to manage the Pod network address space. Pods to be created can be assigned IP addresses from a Pod IP Pool.
type: calico # Specify "calico" for this field if Calico is used as your CNI plugin. "none" means that Pod IP Pools are disabled.
topology: # Use Service Topology to view Service-to-Service communication based on Weave Scope.
type: none # Specify "weave-scope" for this field to enable Service Topology. "none" means that Service Topology is disabled.
openpitrix: # An App Store that is accessible to all platform tenants. You can use it to manage apps across their entire lifecycle.
store:
enabled: true # Enable or disable the KubeSphere App Store.
servicemesh: # (0.3 Core, 300 MiB) Provide fine-grained traffic management, observability and tracing, and visualized traffic topology.
enabled: true # Base component (pilot). Enable or disable KubeSphere Service Mesh (Istio-based).
kubeedge: # Add edge nodes to your cluster and deploy workloads on edge nodes.
enabled: true # Enable or disable KubeEdge.
cloudCore:
nodeSelector: {"node-role.kubernetes.io/worker": ""}
tolerations: []
cloudhubPort: "10000"
cloudhubQuicPort: "10001"
cloudhubHttpsPort: "10002"
cloudstreamPort: "10003"
tunnelPort: "10004"
cloudHub:
advertiseAddress: # At least a public IP address or an IP address which can be accessed by edge nodes must be provided.
- "" # Note that once KubeEdge is enabled, CloudCore will malfunction if the address is not provided.
nodeLimit: "100"
service:
cloudhubNodePort: "30000"
cloudhubQuicNodePort: "30001"
cloudhubHttpsNodePort: "30002"
cloudstreamNodePort: "30003"
tunnelNodePort: "30004"
edgeWatcher:
nodeSelector: {"node-role.kubernetes.io/worker": ""}
tolerations: []
edgeWatcherAgent:
nodeSelector: {"node-role.kubernetes.io/worker": ""}
tolerations: []

检查日志

1
2
3
4
5
kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f


# 排查
kubectl describe pod -n namespace name

etcd 监控证书

1
kubectl -n kubesphere-monitoring-system create secret generic kube-etcd-client-certs  --from-file=etcd-client-ca.crt=/etc/kubernetes/pki/etcd/ca.crt  --from-file=etcd-client.crt=/etc/kubernetes/pki/apiserver-etcd-client.crt  --from-file=etcd-client.key=/etc/kubernetes/pki/apiserver-etcd-client.key

# linxu 安装 单节点

1. 准备 kubekey

1
2
3
4
export KKZONE=cn
curl -sfL https://get-kk.kubesphere.io | VERSION=v3.0.7 sh -
chmod +x kk

2. 使用 kubekey 引导安装集群

1
2
3
./kk create cluster [--with-kubernetes version] [--with-kubesphere version]
./kk create cluster --with-kubernetes v1.22.12 --with-kubesphere v3.3.2

这样不是全功能平台

安装后开启功能

  1. admin 用户登录控制台,点击左上角的平台管理,选择集群管理

  2. 点击定制资源定义,在搜索栏中输入 clusterconfiguration ,点击搜索结果查看其详细页面。

    信息

    定制资源定义(CRD)允许用户在不新增 API 服务器的情况下创建一种新的资源类型,用户可以像使用其他 Kubernetes 原生对象一样使用这些定制资源。

  3. 自定义资源中,点击 ks-installer 右侧的 img,选择编辑 YAML

  4. 在该 YAML 文件中,搜索 devops ,将 enabledfalse 改为 true 。完成后,点击右下角的确定,保存配置。

    1
    2
    devops:
    enabled: true # 将“false”更改为“true”。
  5. 在 kubectl 中执行以下命令检查安装过程:

    1
    kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f

# linux 多节点安装

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
# master节点
export KKZONE=cn
curl -sfL https://get-kk.kubesphere.io | VERSION=v3.0.7 sh -
chmod +x kk

./kk create config --with-kubernetes v1.22.12 --with-kubesphere v3.3.2

vim config.sample
spec:
hosts:
- {name: master, address: 192.168.0.2, internalAddress: 192.168.0.2, user: ubuntu, password: Testing123}
- {name: node1, address: 192.168.0.3, internalAddress: 192.168.0.3, user: ubuntu, password: Testing123}
- {name: node2, address: 192.168.0.4, internalAddress: 192.168.0.4, user: ubuntu, password: Testing123}
roleGroups:
etcd:
- master
control-plane:
- master
worker:
- node1
- node2
controlPlaneEndpoint:
domain: lb.kubesphere.local
address: ""
port: 6443



./kk create cluster -f config-sample.yaml

多节点安装 (kubesphere.io)

# 部署应用

# 使用镜像部署

deploy:部署无状态应用

stateful:部署有状态应用,中间件

daemon:

image-20230413152836946

工作负载 -> PVC -> configmap -> service -> ingress

pvc 建议现用现创

1
2
3
4
# 建议部署流程
1.定义配置字典
2.应用负载-创建工作负载
3.创建工作负载时创建持久卷

# 使用应用市场部署

image-20230414155446759

# 使用应用模板部署

Helm

Artifact Hub

1
2
3
4
5
1.找到仓库地址
https://charts.bitnami.com/bitnami
2.找到对应的工作空间
添加应用仓库
创建应用时从应用模板创建

image-20230414155403399

image-20230414155326651

# 部署 ruoyi cloud

# 本地部署

1. 下载 ruoyi cloud 源码

RuoYi-Cloud: 🎉 基于 Spring Boot、Spring Cloud & Alibaba 的分布式微服务架构权限管理系统,同时提供了 Vue3 的版本 (gitee.com)

2. 下载 nacos

Nacos 快速开始

3. 修改配置文件

application.properties

1
2
3
4
5
6
7
8
9
10
spring.datasource.platform=mysql
# spring.sql.init.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root123

创建数据库 nacos

image-20230414161432325

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
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info */
/******************************************/
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
`encrypted_data_key` text NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';

/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';


/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text NOT NULL COMMENT '秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';

/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';

/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';

/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';

/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(20) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text,
`src_ip` varchar(50) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`encrypted_data_key` text NOT NULL COMMENT '秘钥',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';


/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';


CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';

CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY,
`password` varchar(500) NOT NULL,
`enabled` boolean NOT NULL
);

CREATE TABLE `roles` (
`username` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);

CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);

INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);

INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');

1
2
startup.cmd -m standalone
http://localhost:8848/nacos/#/configurationManagement?dataId=&group=&appName=&namespace=&pageSize=&pageNo=
1
2
3
4
5
6
7
8
导入
ry_config_20220929.sql

# 修改配置文件

db.url.0=jdbc:mysql://127.0.0.1:3306/ry-config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root123
1
2
# 创建
ry_20230223.sql

启动 service

# 上云部署

# 1. 移至 mysql

将本地 mysql 使用 mysql workbench migrate 到云上

image-20230415151254966

移植成功

image-20230415151322174

# 2. 部署中间件 nacos

image-20230415152614222

kubesphere 中创建 nacos service

1
2
nacos-v1-0.nacos.hos.svc.cluster.local:8848
nacos-v1-1.nacos.hos.svc.cluster.local:8848

# 部署微服务

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM openjdk:8-jdk
LABEL maintainer=leifengyang


#docker run -e PARAMS="--server.port 9090"
ENV PARAMS="--server.port=8080 --spring.profiles.active=prod --spring.cloud.nacos.discovery.server-addr=his-nacos.his:8848 --spring.cloud.nacos.config.server-addr=nacos.hos:8848 --spring.cloud.nacos.config.namespace=prod --spring.cloud.nacos.config.file-extension=yml"
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone

COPY target/*.jar /app.jar
EXPOSE 8080

#
ENTRYPOINT ["/bin/sh","-c","java -Dfile.encoding=utf8 -Djava.security.egd=file:/dev/./urandom -jar app.jar ${PARAMS}"]

打包:
maveh 打成可执行 jar, 上传给服务器 (master)

制作镜像:docker 根据 Dockerfile 把包打成指定的镜像

推送镜像:将镜像推送给 docker hub(阿里云镜像仓库)

应用部署:给 k8s 部署应用,node1 节点部署应用

docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/shinya/ 镜像名:v1

image-20230416213506519

若依管理系统

# DevOps

DevOps 是一系列做法和工具,可以使 IT 和软件开发团队之间的流程实现自动化。其中,随着敏捷软件开发日趋流行,持续集成 (CI)持续交付 (CD) 已经成为该领域一个理想的解决方案。在 CI/CD 工作流中,每次集成都通过自动化构建来验证,包括编码、发布和测试,从而帮助开发者提前发现集成错误,团队也可以快速、安全、可靠地将内部软件交付到生产环境。

https://gitee.com/leifengyang/yygh-parent

https://gitee.com/leifengyang/yygh-admin

https://gitee.com/leifengyang/yygh-site

中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sentinel.hos:8080
http://106.14.22.128:30395/#/login


mongodb.hos:27017
http://106.14.22.128:30417

redis.hos


mysql.hos


rabbitm-j17f1z-rabbitmq.hos

mvn clean package -Dmaven.test.skip=true

  • 使用 admin 登陆 ks

  • 进入集群管理

  • 进入配置中心

  • 找到配置

    • ks-devops-agent
    • 修改这个配置。加入 maven 阿里云镜像加速地址
    • 使用 admin 登陆 ks
    • 进入集群管理
    • 进入配置中心
    • 找到配置
      • ks-devops-agent
      • 修改这个配置。加入 maven 阿里云镜像加速地址
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
pipeline {
agent {
node {
label 'maven'
}

}
stages {
stage('拉取代码') {
agent none
steps {
container('maven') {
git(url: 'https://gitee.com/kbshire/yygh-parent', credentialsId: 'giteeid', branch: 'master', changelog: true, poll: false)
sh 'ls -al'
}

}
}

stage('项目编译') {
agent none
steps {
container('maven') {
sh 'ls -al'
sh 'mvn clean package -Dmaven.test.skip=true'
}

}
}

stage('default-2') {
parallel {
stage('构建hospital镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t hospital-manage:latest -f hospital-manage/Dockerfile hospital-manage/'
sh 'ls -al hospital-manage/target'
}

}
}
stage('构建server-gateway') {
agent none
steps {
container('maven') {
sh 'ls -al server-gateway/target'
sh 'docker build -t server-gateway:latest -f server-gateway/Dockerfile server-gateway/'

}

}
}
stage('构建service-cmn镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-cmn:latest -f service/service-cmn/Dockerfile ./service/service-cmn/'
sh 'ls -al service/service-cmn/target'
}

}
}
stage('构建service-hosp镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-hosp:latest -f service/service-hosp/Dockerfile ./service/service-hosp/'
sh 'ls -al service/service-hosp/target'
}

}
}
stage('构建service-order镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-order:latest -f service/service-order/Dockerfile ./service/service-order/'
sh 'ls -al service/service-order/target'
}

}
}
stage('构建service-oss镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-oss:latest -f service/service-oss/Dockerfile ./service/service-oss/'
sh 'ls -al service/service-oss/target'
}

}
}
stage('构建service-sms镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-sms:latest -f service/service-sms/Dockerfile ./service/service-sms/'
sh 'ls -al service/service-sms/target'
}

}
}
stage('构建service-statistics镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-statistics:latest -f service/service-statistics/Dockerfile ./service/service-statistics/'
sh 'ls -al service/service-statistics/target'
}

}
}
stage('构建service-task镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-task:latest -f service/service-task/Dockerfile ./service/service-task/'
sh 'ls -al service/service-task/target'
}

}
}
stage('构建service-user镜像') {
agent none
steps {
container('maven') {
sh 'docker build -t service-user:latest -f service/service-user/Dockerfile ./service/service-user/'
sh 'ls -al service/service-user/target'
}

}
}






}
}

stage('push latest') {
when {
branch 'master'
}
steps {
container('maven') {
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest '
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest '
}

}
}

stage('deploy to dev') {
steps {
container('maven') {
input(id: 'deploy-to-dev', message: 'deploy to dev?')
withCredentials([kubeconfigContent(credentialsId : 'KUBECONFIG_CREDENTIAL_ID' ,variable : 'KUBECONFIG_CONFIG' ,)]) {
sh 'mkdir -p ~/.kube/'
sh 'echo "$KUBECONFIG_CONFIG" > ~/.kube/config'
sh 'envsubst < deploy/dev-ol/deploy.yaml | kubectl apply -f -'
}

}

}
}

stage('deploy to production') {
steps {
container('maven') {
input(id: 'deploy-to-production', message: 'deploy to production?')
withCredentials([kubeconfigContent(credentialsId : 'KUBECONFIG_CREDENTIAL_ID' ,variable : 'KUBECONFIG_CONFIG' ,)]) {
sh 'mkdir -p ~/.kube/'
sh 'echo "$KUBECONFIG_CONFIG" > ~/.kube/config'
sh 'envsubst < deploy/prod-ol/deploy.yaml | kubectl apply -f -'
}

}

}
}

}
environment {
//定义的所有变量可以在任意位置使用
DOCKER_CREDENTIAL_ID = 'dockerhub-id'
GITHUB_CREDENTIAL_ID = 'github-id'
KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig'
REGISTRY = 'docker.io'
DOCKERHUB_NAMESPACE = 'docker_username'
GITHUB_ACCOUNT = 'kubesphere'
APP_NAME = 'devops-java-sample'
}
parameters {
string(name: 'TAG_NAME', defaultValue: '', description: '')
}
}
1
2
3
4
5

registry.cn-hangzhou.aliyuncs.com/shinya_hello/ruoyi-visual-monitor:

sh 'docker tag hospital-manage:latest $registry/$DOCKERHUB_NAMESPACE/hospital-manage:SNAPSHOT-$BUILD_NUMBER'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/hospital-manage:SNAPSHOT-$BUILD_NUMBER'

加载阿里云镜像密钥

image-20230418174358540

http://106.14.22.128:30880/devops_webhook/git/?url=https://gitee.com/kbshire/yygh-site

http://106.14.22.128:30880/devops_webhook/git/?url=https://gitee.com/kbshire/yygh-admin

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

John Doe WeChat Pay

WeChat Pay

John Doe Alipay

Alipay

John Doe PayPal

PayPal