kubernetes之实战进阶篇

目录

一、搭建kubenetes集群

1.1、搭建方案选择

1.2、软硬件准备

1.2.1、硬件要求:

1.2.2、软件要求

1.3、安装步骤

1.3.1、初始化操作(三个节点都要执行一遍)

[1.3.2、部署kubernetes master节点(控制面板)](#1.3.2、部署kubernetes master节点(控制面板))

1.3.3、node节点加入k8s集群

1.3.4、部署CNI网络插件

1.3.5、验证部署是否成功

二、kubectl命令行工具

2.0、在任意节点使用kubectl

2.1、命令行工具kubectl

注意:执行相关指令的时候一定要注意指定namespace。

2.1.1、常用资源类型与别名

2.1.2、资源相关操作

2.1.3、pod与集群操作

2.1.4、格式化输出

2.2、API概述

2.2.1、API类型

2.2.2、访问控制

2.2.3、弃用api

三、深入Pod

3.1、pod配置文件

3.2、探针(Probe)

[3.2.1、探针类型 ------ StartUpProbe(启动探针)](#3.2.1、探针类型 —— StartUpProbe(启动探针))

[3.2.2、探针类型 ------ LivessProbe(存活探针)](#3.2.2、探针类型 —— LivessProbe(存活探针))

[3.2.3、探针类型 ------ ReadlinessProbe(就绪探针)](#3.2.3、探针类型 —— ReadlinessProbe(就绪探针))

[3.2.4、探针方式 ------ExecAction](#3.2.4、探针方式 ——ExecAction)

[3.2.5、探针方式 ------TCPSocketAction](#3.2.5、探针方式 ——TCPSocketAction)

[3.2.6、探针方式 ------HTTPGetAction](#3.2.6、探针方式 ——HTTPGetAction)

3.2.7、其他常用参数配置

3.3、探针效果验证

3.3.1、startupProbe配置httpGet+不存在路径

3.3.2、startupProbe配置httpGet+存在的路径

3.3.3、startupProbe配置tcpSocket+80端口

3.3.4、startupProbe配置执行命令的方式

3.3.5、验证存活探针

3.4、生命周期

3.3.1、退出流程

3.3.2、PreStop的应用

四、资源调度

4.0、Label和Selector

4.0.1、Label的添加、修改与查看

4.0.2、Selector的使用

4.1、Deployment

4.1.1、创建

4.1.2、滚动更新

4.1.3、回滚

4.1.4、扩缩容

4.1.5、暂停与恢复

4.2、StatefulSet

4.2.1、创建

4.2.2、扩容缩容

4.2.3、镜像更新

4.2.4、删除

4.3、DaemonSet

4.3.1、配置文件

4.3.2、指定Node节点

4.3.3、滚动更新

4.4、HPA自动扩/缩容

[4.4.1、安装 metrics-server(指标服务)](#4.4.1、安装 metrics-server(指标服务))

4.4.2、创建nginx-deploy用于验证效果

4.4.3、创建service

五、服务发布

5.1、service

5.1.1、service介绍与基础操作

5.1.2、service代理k8s外部服务

5.1.3、service反向代理外部域名

5.1.4、service的四种常用类型

5.2、Ingress

5.2.1、Ingress的安装

5.2.2、Ingress的基本使用

6、配置管理

6.1、ConfigMap

[6.2、加密数据配置 Secret](#6.2、加密数据配置 Secret)

6.3、SubPath的使用

6.4、不可变的secret和configmap

7、持久化存储

7.1、Volumes(依赖本机实现)

7.1.1、HostPath

7.1.2、EmptyDir

7.2、NFS挂载(依赖远端实现)

7.2.1、nfs介绍

7.2.2、nfs安装与基本功能使用(跨节点的文件共享)

7.2.3、nfs服务挂载到容器

7.3、PV与PVC

7.3.1、PV与PVC的概念

7.3.2、PV的生命周期

7.3.3、PV的使用

7.3.4、PVC的使用

7.4、StorageClass(存储类)

7.4.1、制备器(Provisioner)

7.4.2、NFS动态制备案例

8、高级调度

8.1、CronJob计划任务

8.2、初始化容器

8.3、污点(Taint)和容忍(Toleration)

[8.3.1、误点(Taint) ------ 打在节点上](#8.3.1、误点(Taint) —— 打在节点上)

[8.3.2、容忍(Toleration) ------ 打在任务(Pod)上](#8.3.2、容忍(Toleration) —— 打在任务(Pod)上)

8.4、亲和力(Affinity)

8.3.1、节点亲和力(NodeAffinity)

​编辑

8.3.2、Pod亲和力(PodAffinity)与Pod反亲和力(PodAntiAffinity)

7、身份与权限认证

7.1、认证

7.2、授权(RBAC)


一、搭建kubenetes集群

1.1、搭建方案选择

  • (1)minikube: 更轻量适合自己个人电脑搭建,这里不做过多介绍。
  • (2)命令行安装: 一键安装 太简单以至于学不到东西,不做过多介绍。
  • (3)二进制安装: 手动下载二进制文件、手动配置组件,更多用于自学和实践。
  • (4)kubeadm: 相对比较完整,资源占用的也较多;个人电脑可能会卡。

kubeadm是官方社区推出的用于快速部署kubernetes集群的工具。

使用kubeadm创建k8s集群是使用预先打包好的二进制文件安装,在部署方面更自动化易于维护。另外kubeadm是kubernetes的官方工具,有更好的社区支持(文档和帮助)。二进制安装则需要更多的自学和实践。此处用于学习和测试所以选择采用kubeadm的方式进行安装。

官网首页就介绍了这些方式:

https://kubernetes.io/docs/home/

安装步骤其实都在这里:

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

关于安装方法可以参照官网 利用kubeadm创建高可用集群.

https://kubernetes.io/zh-cn/docs/setup/production-environment/tools/kubeadm/high-availability/

1.2、软硬件准备

1.2.1、硬件要求:

每台服务器至少要2核才行,否则后面可能会报错。

准备三台云服务器:

  • 2核4G机器119.45.118.94(内10.206.32.xx) docker 25.0.4 opencloudos:8(就是centos8)
  • 1核2G机器118.195.193.69(内10.206.0.xx) docker 25.0.3 CentOS-7(配置态度,废弃)
  • 2核2G机器175.27.156.69(内10.206.0.xx) docker 25.0.4 opencloudos:8(就是centos8)
  • 2核2G机器129.211.213.34(内10.206.32.xx) docker 25.0.4 opencloudos:8(就是centos8)
1.2.2、软件要求

根据官网我们知道k8s 1.24版本之后就需要额外地安装cri-dockerd作为桥接才能使用Docker Egine。经过尝试1.24后的版本麻烦事很多,所以此处我们选择1.23.6版本。

1.3、安装步骤

1.3.1、初始化操作(三个节点都要执行一遍)

(1)环境初始化

#关闭防火墙(后续挨个开放端口太麻烦)
systemctl stop firewalld
systemctl disable firewalld

#关闭SeLinux 
setenforce 0
sed -i 's/enforcing/disabled/' /etc/selinux/config  

注:SELinux(Security-Enhanced Linux)是一种强制访问控制(MAC)的安全机制,旨在提高Linux操作系统的安全性、可靠性和稳定性。

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

#将桥接的IPV4流量传递到iptables
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

sysctl --system    #使生效

注:关于为什么要开启这个内核参数可以参考 https://zhuanlan.zhihu.com/p/414500615?utm_id=0 


#时间同步(centos7)
yum install ntpdate -y
ntpdate time.windows.com

注:如果是centos8,ntdate被换成了chrony。这里本身就是腾讯云的服务器,感觉不用单独搞这个。
具体处理方式如下: https://blog.csdn.net/IT_ZRS/article/details/112553404

#在三台机器上分别设置对应的hostname
hostnamectl set-hostname k8s-master
hostnamectl set-hostname k8s-node1
hostnamectl set-hostname k8s-node2

#为了后续可读性更强,可以再修改下主机名(其实就是往/etc/hosts中添加<ip,域名>的映射)
cat >> /etc/hosts << EOF
10.206.32.9 k8s-master
10.206.0.10 k8s-node1
10.206.32.7 k8s-node2
EOF

(2)安装基础软件(所有节点)

1)安装docker

参见之前docker的文章 docker学习入门篇-CSDN博客

2)添加下kubernetes软件源仓库

定义kubernetes源(三个节点都要执行)

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

3)安装kubeadm、kubelet、kubectl (master/node都需要执行)

#安装三个组件命令 切记,这里安装1.24之前的版本,否则需要cri-dockerd挺麻烦的。
yum install -y kubelet-1.23.6 kubeadm-1.23.6 kubectl-1.23.6


#开机自启kubelet
systemctl enable kubelet.service
注:K8S通过kubeadm安装出来以后都是以Pod方式存在,即底层是以容器方式运行,所以kubelet必须设置开机自启

#查看版本
kubelet --version

#如果已经安装1.24之后的版本要卸载重新安装
yum remove kubelet kubeadm kubectl

4)配置cgroupdriver为"systemd"(所有节点)

不改成systemd的话后续kubadm init的时候回报错。其实在安装docker配置镜像源的时候配置下就好了。

驱动不能用 cgroupfs,要换成 systemd。

#查看驱动类型
docker info | grep Driver

#打开/etc/docker/daemon.json文件添加如下一行。注意逗号的问题。
"exec-opts": ["native.cgroupdriver=systemd"],

例如添加后的整体文件如下。
{
   "exec-opts": ["native.cgroupdriver=systemd"],
   "registry-mirrors": [
       "https://mirror.ccs.tencentyun.com",
       "https://hub-mirror.c.163.com",
       "https://mirror.baidubce.com"
   ]
}

#重新加载并重启
systemctl daemon-reload
systemctl restart docker

#如果已经安装了kubelet建议这里在重启下kubelet
systemctl restart kubelet


#使用Systemd管理的Cgroup来进行资源控制与管理,因为相对Cgroupfs而言,Systemd限制CPU、内存等资源更加简单和成熟稳定。
#日志使用json-file格式类型存储,大小为100M,保存在/var/log/containers目录下,方便ELK等日志系统收集和管理日志。
1.3.2、部署kubernetes master节点(控制面板)

只需要在master节点执行,这里的apiserver需修改成自己的地址。

注意:如果是云服务器的话这里用内网地址,而不是外网地址(ifconfig显示的是啥就用啥)。

kubeadm init \
--apiserver-advertise-address=10.206.32.9 \
--image-repository registry.aliyuncs.com/google_containers \
--kubernetes-version v1.23.6 \
--service-cidr=10.96.0.0/12 \
--pod-network-cidr=10.244.0.0/16

------------------------#解释字段----------------------------------------------
--apiserver-advertise-address   //apiserver通告给其他组件的IP地址,一般应该为Master节点的用于集群内部通信的IP地址,0.0.0.0表示节点上所有可用地址
--apiserver-bind-port  //apiserver的监听端口,默认是6443
--cert-dir  //通讯的ssl证书文件,默认/etc/kubernetes/pki
--control-plane-endpoint  //控制台平面的共享终端,可以是负载均衡的ip地址或者dns域名,高可用集群时需要添加
--image-repository   //拉取镜像的镜像仓库,默认是k8s.gcr.io
--kubernetes-version //指定kubernetes版本
--pod-network-cidr  //pod资源的网段,需与pod网络插件的值设置一致。Flannel网络插件的默认为10.244.0.0/16,Calico插件的默认值为192.168.0.0/16;
--service-cidr  //service资源的网段
--service-dns-domain //service全域名的后缀,默认是cluster.local
--token-ttl   //默认token的有效期为24小时,如果不想过期,可以加上 --token-ttl=0 这个参数
 
 
# service-cidr 和 pod-network-cidr 最好就用这个,不然需要修改后面的 kube-flannel.yaml 文件
 
这种方式初始化后需要修改 kube-proxy 的 configmap,开启 ipvs
kubectl edit cm kube-proxy -n=kube-system
修改mode: ipvs

shit, 终于行了!!!!!!

之后就要按照第二个红框的提示来配置我们的kubectl,使kubectl也能够使用!!!

#其实就是复制这三条命令并执行就可以了

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

执行好后验证下kubectl是不是可以使用了。

#获取默认命名空间的pod
kubectl get po

#加上-A无视命名空间全局查找
kubectl get po -A

#获取节点(显然此时只有一个节点)
kubectl get nodes

如下可以成功执行就说明ok了。。。

注:一个集群至少有一个master一个node,现在没有node所以是noready的;接下来我们加入node节点。

注:期间如果一直起不来我们可以通过如下指令查看日志原因。

#这是个好指令,务必掌握

journalctl -xefu kubelet

如果报的是类似如下"没有认证"之类的信息可以先不用关注。

1.3.3、node节点加入k8s集群

注:这里master_ip用的也是内网ip,用外网ip貌似不行(提示docker版本太新了)。

#指令形式如下:
kubeadm join master_ip:6443 --token <master控制台的token> \
    --discovery-token-ca-cert-hash <master控制台的hash> 

说明:
(0)master_ip对于云服务器的ip这里用内网ip
(1)6443是固定的端口,默认就是这个
(2)如果控制台清空了,token可以通过下面介绍的指令获得
(3)hash用下面介绍指令执行(前面记得拼接上"sha256:")

#查看token(没过期就用,过期就重新生成)
kubeadm token list

#获取hash值
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'

#在node1和node2上分别执行如下指令(注:用内网ip)
kubeadm join 10.206.32.9:6443 --token bje943.cm1gbmsu8ft3xi0u \
--discovery-token-ca-cert-hash sha256:8de37960dac1f9f6f65fa4bbd6083fc7423df8e57300b8acce534776aade9a3b

node 节点加入集群需要生成的 token,token 有效期为 24 小时,过期需要重新创建

#token 过期重新生成 token
kubeadm token create --print-join-command

通过在master上执行 kubectl get no 可以看到执行效果如下:显然,都加进去了。

注:前面hostname没有设置好,所以节点的名字有点不符合预期。

注:此时还不能在node节点执行"kubectl get nodes",因为还没做kubelet的配置。

1.3.4、部署CNI网络插件

这里有两种方式。一种是calico方式、另一种是flannel。这里采用calico的方式。

#首先把官网的yaml模板下载下来
wget https://docs.projectcalico.org/manifests/calico.yaml

#然后修改其中一些字段
CALICO_IPV4POOL_CIDR 修改为和 kubeadm init 的时候 --pod-network-cidr所指定的值。
其他的我们就都默认,不去改了。。 

#执行如下指令发现都是从docker.io去下载镜像这个明显太慢了。
grep image calico.yaml

#为此我们执行如下命令匹配"docker.io"将其替换成空字符串,避免下载过慢导致失败
sed -i 's#docker.io/##g' calico.yaml

#我们也可以先指定docker pull 将其中用到的镜像提前pull下来。
docker pull calico/cni:v3.25.0
docker pull calico/node:v3.25.0
docker pull calico/kube-controllers:v3.25.0

#接下来进行构建操作(聚合文件就要用apply)
kubectl apply -f calico.yaml

查看pod信息,看看是否有calico相关pod。

kubectl get po -n kube-system

如下可以看到calico相关pod已经有了,我们再等一会看看是否都成功启动。

STATUS:

(1)Init:表示在用初始化容器做一些事情,这个时候我们等就好了。

(2)Pending:等一会儿不行就要看看究竟怎么了。

多等一会儿就都起来了。如下。

如果一直有pending,那我们就通过如下指令查看这个pod究竟这么了。

kubectl describe po <pod_name> -n kube-system

注:node1机器配置太低又重新换了一台机器,重新安装docker、k8s在join集群就好了。

从集群中删除节点的操作如下。

#master执行

kubectl drain <node_name> --delete-local-data --force --ignore-daemonsets

kubectl delete node <node_name>

#被删除节点执行

kubeadm reset

1.3.5、验证部署是否成功

(1)首先我们部署一个nginx服务并暴露容器内的80端口

#创建一个nginx服务(创建一个deployment叫nginx)
kubectl create deployment nginx --image=nginx

#暴露一个端口让外部可以访问(相当于为nginx创建了一个service暴露容器内的80端口)
kubectl expose deployment nginx --port=80 --type=NodePort

(2)查看pod和端口映射情况

kubectl get pod,svc

可以看到外部端口对应的是 30680。直接访问三台服务器的此端口,效果如下。

访问三个节点的 外网ip:port都是通的。至此可以确认安装完全成功!!!

二、kubectl命令行工具

注:后面用到的所有指令都可以在官网上搜到。Container Runtimes | Kubernetes

2.0、在任意节点使用kubectl

根据前面理论讲解我们知道kubectl只是个操作入口,实际上是向api-server发送请求。

master之所以可以执行kubectl是因为在 ~/.kube/config 配置中配置了api-server的ip、端口相关信息,但是node节点并没有。所以,只需要让node节点也有此类信息就可以了。

#可以看到相关信息
cat ~/.kube/config
cat /etc/kubernetes/admin.conf

(1)将master节点中的 /etc/kubernetes/admin.conf 拷贝到需要运行的服务器的 /etc/kubernetes 目录。

scp /etc/kubernetes/admin.conf root@k8s-node1:/etc/kubernetes/
scp /etc/kubernetes/admin.conf root@k8s-node2:/etc/kubernetes/
#注:按提示输入指纹还k8s-node1/k8s-node2的密码即可传输过去。

(2)在对应服务器上配置环境变量(就是将双引号部分放入.bash_profile文件)

echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
source ~/.bash_profile

(3)然后就可以执行了

kubectl get nodes

2.1、命令行工具kubectl

关于命令行工具kubectl的使用参见官网: kubectl | Kubernetes

如果不知道一个指令怎么用可以 --help,例如:

kubectl scale --help
kubectl create --help
kubectl run --help
注意:执行相关指令的时候一定要注意指定namespace。
2.1.1、常用资源类型与别名

全称 简写

  1. pods → po
  2. deployment → deploy
  3. services → svc
  4. namespace → ns
  5. nodes → no
  6. Persistent Volume Claim → pvc
  7. daemonset → ds
  8. endpoint → ep
  9. configmap → cm
  10. PersistentVolume → pv
  11. PersistentVolumeClaim → pvc
  12. CronJob → cj
  13. StorageClass → sc
2.1.2、资源相关操作

(1)创建对象 ------ kubectl create

#例如我们此处创建一个nginx
kubectl create deployment nginx --image=nginx


#-f执行配置文件创建对象
kubectl create -f ./my-manifest.yaml

#可以指定多个配置文件
kubectl create -f ./my-f1.yaml ./my-f2.yaml

#可以执行文件夹
kubectl create -f ./dir

#可以指定一个远程的配置文件
kubectl create -f https://gigt.io/vPieo

#直接run也会创建,创建并运行一个容器
kubectl run nginx --image=nginx

(2)显示和查找对象

#获取资源信息(想获取什么资源就get什么资源)
kubectl get po/ns/deploy/svc

#在default命名空间下获取pod(可以看到有nginx)
kubectl get po 

#获取customer命名空间下的pod,并筛选mongo关键词
kubectl get po -n customer | grep mongo

#查找全局的pod(所有命名空间)
kubectl get po -A | grep mongo

#在指定命名空间下获取pod
kubectl get po -n default
kubectl get po -n kube-system

#通过 -o wide 查看详细信息(当前容器ip是多少,被部署在哪个节点)
kubectl get po -o wide

#查看有哪些命名空间(很多指令都要正确指定namespace)
kubectl get ns

#查看deploy信息
kubectl get deploy

注:READY了才是真正可用!!!!

#查看某个pod的情况(一般用于pod状态异常的原因查看)
kubectl describe po <po_name>

(3)进入容器

#进入default命令空间中的nginx-po这个pod
kubectl exec -it nginx-po  -- /bin/bash


#进入default命名空间下的dns-test这个pod
kubectl exec -it dns-test -- sh

#进入命名空间customer下的名字为mongo-proxy-648bbcc474-7ftjg的pod
kubectl exec --stdin -it mongo-proxy-648bbcc474-7ftjg --namespace=customer -- /bin/bash

(4)更新资源

#修改yaml文件后执行repalce即可,非常通用
kubectl replace -f zs-ingress.yaml

kubectl replace -f uc_msg_svr.yaml

(5)修补资源

(6)编辑资源

#通过编辑我们就可以查看对应资源的yaml
kubectl edit po/deploy/svc <name> -n <namespace>

#编辑coredns这个deploy
kubectl edit deploy coredns -n kube-system

#编辑pod
kubectl edit po nginx-demo

注意:我们直接执行edit尝试去更新pod发现会报错。

kubectl edit po nginx-demo

原因:pod就是最原生的东西,它自身没有动态更新、故障恢复等功能是不能直接更改的。我们可以通过deployment之类的进行更改。

(7)scale资源

官网说明:Kubectl Reference Docs

注意:这个指令是对deployment、stateful这个维度操作的,而不是pod。pod是最原始的描述信息了。

Set a new size for a deployment, replica set, replication controller, or stateful set.

#此处我们对前面创建的nginx的那个deploy进行扩容(注:这个nginx是deploy的名字)
kubectl scale deploy --replicas=3 nginx

#然后执行kubectl get po就可以看到有三个pod了。
kubectl get po
kubectl get deploy

也就是这个deploy下面有了三个pod!!

# Scale a replica set named 'foo' to 3
  kubectl scale --replicas=3 rs/foo
  
# Scale a resource identified by type and name specified in "foo.yaml" to 3
  kubectl scale --replicas=3 -f foo.yaml
  
# If the deployment named mysql's current size is 2, scale mysql to 3
  kubectl scale --current-replicas=2 --replicas=3 deployment/mysql
  
# Scale multiple replication controllers
  kubectl scale --replicas=5 rc/foo rc/bar rc/baz

(8)删除资源

deployment被删了后,里面的pod肯定也都没了。

deploy存在的情况下会发现直接删除pod不符合预期,因为deploy会重新建立新的Pod。

#删除资源(想删除什么资源就delete什么资源)
kubectl delete deploy <deploy_name>
kubectl delete svc <svc_name>

#查看得知这个deploy的名字是nginx
kubectl get deploy
Kubectl get deploy | grep xxx

#删除nginx这个deployment
kubectl delete deploy nginx

#查看有哪些service
kubectl get svc

#将前面创建的service给删除
kubectl delete svc nginx


#删除default命令空间下的pod nginx-demo
kubectl delete po nginx-demo

(9)拷贝命令

#将宿主机的 test.html 文件拷贝至nginx-po的/usr/share/nginx/html/目录下
kubectl cp test.html nginx-po:/usr/share/nginx/html/
2.1.3、pod与集群操作

(1)与运行的pod交互

(2)与节点和集群交互

2.1.4、格式化输出

(1)输出json格式 -o json

(2)仅打印资源名称 -o name

(3)以纯文本格式输出所以信息 -o wide

(4)输出yaml格式 -o yaml

假设想要写个deployment但是配置文件又不知道该怎么写,那么可以把已有的导出出来看看。

kubectl get deploy nginx -o yaml

输出内容如下:我们重点就是关注其中的template部分,就是概念篇中介绍的PodTemplate。

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: "2024-03-17T03:06:28Z"
  generation: 2
  labels:
    app: nginx
  name: nginx
  namespace: default
  resourceVersion: "111295"
  uid: 0077ac03-21ae-4d51-b1f1-acd1d4035e9c
spec:
  progressDeadlineSeconds: 600
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
  observedGeneration: 2
  readyReplicas: 3
  replicas: 3
  updatedReplicas: 3

2.2、API概述

本质上将包括前面的kubectl命令行工具、后边编写配置文件最终其实都是调用的api。

k8s又更细的比较频繁,不同版本的api也会有些差异;所以还是要了解下api的一些东西。

关于api的概念可以参考中文官网: Kubernetes API 概念 | Kubernetes

REST API是k8s系统的重要部分,组件之间的所有操作和通信都是由API Server处理的REST API调用,大多数情况下API的定义和实现都符合 HTTP REST格式。

2.2.1、API类型

主要有以下三个版本:

Alpha:尝鲜版 可能有bug,对于某个新的api也可能后续没了;不建议使用。

Beta:已经经过很好的测试,启用功能被认为是安全的;细节可能会变,但是不会删除。

Stable:确定且稳定的版本,用的最多。

2.2.2、访问控制

认证与授权。也就是说有相应的权限后才可以调用相应的api,后面介绍。

2.2.3、弃用api

由于更新的比较快所以看到的很多文档说明都是过时的,我们可以在官网看到弃用说明。

已弃用 API 的迁移指南 | Kubernetes 中文,有时候不对。

Deprecated API Migration Guide | Kubernetes 英文,更权威、更准确。

三、深入Pod

3.1、pod配置文件

前言:如果不用可视化UI工具的话后续我们的操作都是kubectl + 配置文件。

创建 nginx-demo.yaml 配置文件:内容如下。

关于更多资源清单用的的时候再看,不用去记知道会用就行。

Kubernetes-资源清单_kubernetes 资源清单-CSDN博客

apiVersion: v1 #api文档版本
kind: Pod  #资源对象类型,也可以配置为Deployment,StatefulSet等对象
metadata:  #Pod相关元数据,用于描述pod的数据
  name: nginx-demo #Pod的名称
  labels:          #pod的标签(具体key-value自己决定)
    type: app       #自定义label标签, 名字为type值为app
    test: 1.0.0     #自定义label标签, 描述Pod版本
  namespace: "default" #命名空间
spec:      #期望pod按照这个描述进行创建
  containers:  #对于pod中的容器的描述
  - name: nginx  #容器的名字,自己随便取
    image: nginx #指定容器的镜像(版本),必须是docker中可用的镜像
    imagePullPolicy: IfNotPresent  #镜像拉取策略.此处指定本地有就用本地没有就拉取
    command: #指定容器启动时候执行的命令
    - nginx
    - -g
    - 'daemon off;'  #ngixn -g 'daemon off';
    workingDir: /usr/share/nginx/html  #定义进入容器后默认停留的目录
    ports:
    - name: http #端口名称
      containerPort: 80 #描述容器内暴露什么端口
      protocol: TCP  #描述改端口是基于哪种协议通讯的
    env:       #环境变量
    - name: JVM_OPTS     #环境变量名称
      value: '-Xms128m -Xmx128m'  #环境变量的值
    resources:
      requests:  #最少需要多少资源
        cpu: 100m  #限制cpu最少使用0.1个核心(1000m就代表一个核心)
        memory: 128Mi #限制内存最少占用128M
      limits:     #最多的限制
        cpu: 200m     #cpu最多受用0.2核心
        memory: 256Mi #内存最多使用256M
  restartPolicy: OnFailure #重启策略 只有失败的时候才会重启

3.2、探针(Probe)

容器内应用的监测机制,根据不同的探针来判断容器应用当前的状态。

简单来说:就是发现服务是不是挂了,然后重启服务;非常强大的功能。

至于yaml怎么写有两种方式。①查官网 ②找现成的pod的yaml进行copy。

3.2.1、探针类型 ------ StartUpProbe(启动探针)

k8s 1.6版本新增的探针,用于判断应用程序是否已经启动了。当配置了StartUpProbe后,会先禁用其他探针直到StartUpProbe成功后其他探针才会继续。
简单来说:它的作用就是用于判断应用是否启动,启动后后续的LivessProbe/ReadlinessProbe 探针就可以做自己该做的事情了。

注意:后面两个探针都是在应用启动成功之后才去监听是否正常的.应用都没有启动成功去监听并起作用显然是不行的。例如:应用启动的时间是11s,LivessProbe检测间隔是10s,这样程序就永远启动不起来了。

3.2.2、探针类型 ------ LivessProbe(存活探针)

简单来说:检测到容器失败就进行重启,实现故障自动恢复的效果。

用于探测容器中的应用是否运行,如果探测失败,kubelet会根据配置的重启策略进行重启,若没有配置,默认就认为容器自动成功不会执行重启策略。

查看一个存活探针的例子:执行如下指令可以看到存活探针配置如下。

kubectl get deploy -n kube-system
kubectl edit deploy coredns -n kube-system

        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
3.2.3、探针类型 ------ ReadlinessProbe(就绪探针)

简单来说:决定是否可以接受外部流量,如果成功就能接受否则就不接受外部流量。

我们可以假设一个场景:服务启动后我们可能还需要做些额外的操作服务才可用,例如需要把加载数据到内存等。也就是说容器真正初始化完成以前都是不可用的(即便服务进程已经启动)。

ReadlinessProbe 的作用就是探测容器内的程序是否健康,如果返回success纳闷就认为改容器已经完全启动,并且该容器可以接受外部流量了。

        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /ready
            port: 8181
            scheme: HTTP
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
3.2.4、探针方式 ------ExecAction

在容器内部执行一个命令;如果返回时为0表示任务容器是健康的,否则失败。

例如检查某个文件夹下的某文件是否存在,存在说明成功,不存在说明失败。

3.2.5、探针方式 ------TCPSocketAction

对配置的tcp端口进行连接,如果能连接上就说明成功了,如果不能就说明失败的。

3.2.6、探针方式 ------HTTPGetAction

配置http请求(包括请求路径、端口),每隔一段时间就发送一个http请求到容器内的应用程序,如果接口返回的状态码在200~400之间,则认为容器健康。

3.2.7、其他常用参数配置
  • initailDelaySeconds: 60 #不配置启动探针了而是配置一个固定时间;此时间内存活探针、就绪探针都不执行。注: 一定程度上可以代替启动探针。但是这毕竟是一个写死的时间,适用性肯定是比不了启动探针的。
  • timeoutSeconds: 2 #超时时间。前面tcp、http检测的超时时间,超过此时间就认为失败。
  • periodSeconds: 5 #间隔时间。上一次执行失败了,间隔多久在进行检测。
  • successThreshold: 1 #成功阈值。成功几次才认为成功。
  • failureThreshold: 2 #失败阈值。监测失败2次就认为失败(避免网络抖动)。

3.3、探针效果验证

创建如下名为nginx-po.yaml的文件。

注意:其中增加了启动探针startupProbe的配置。但是配的是一个不存在的路径"/api/path"。

预期:容器肯定启动不起来。

后续我们在把路径换成"index.html"进行测试。注:对于nginx来说这个路径是有的。

预期:此时可以起来。

apiVersion: v1 #api文档版本
kind: Pod  #资源对象类型,也可以配置为Deployment,StatefulSet等对象
metadata:  #Pod相关元数据,用于描述pod的数据
  name: nginx-po #Pod的名称
  labels:          #pod的标签(具体key-value自己决定)
    type: app       #自定义label标签, 名字为type值为app
    test: 1.0.0     #自定义label标签, 描述Pod版本
  namespace: "default" #命名空间
spec:      #期望pod按照这个描述进行创建
  containers:  #对于pod中的容器的描述
  - name: nginx  #容器的名字,自己随便取
    image: nginx #指定容器的镜像(版本),必须是docker中可用的镜像
    imagePullPolicy: IfNotPresent  #镜像拉取策略.此处指定本地有就用本地没有就拉取
    startupProbe: #应用启动探针配置
      httpGet: #探针方式,基于http请求探针
        path: /api/path #http请求路径
        port: 80 #请求的端口
      failureThreshold: 3 #失败多少次算真正失败
      periodSeconds: 10 #间隔时间
      successThreshold: 1 #多少次监控算成功
      timeoutSeconds: 5 #请求的超时时间
    command: #指定容器启动时候执行的命令
    - nginx
    - -g
    - 'daemon off;'  #ngixn -g 'daemon off';
    workingDir: /usr/share/nginx/html  #定义进入容器后默认停留的目录
    ports:
    - name: http #端口名称
      containerPort: 80 #描述容器内暴露什么端口
      protocol: TCP  #描述改端口是基于哪种协议通讯的
    env:       #环境变量
    - name: JVM_OPTS     #环境变量名称
      value: '-Xms128m -Xmx128m'  #环境变量的值
    resources:
      requests:  #最少需要多少资源
        cpu: 100m  #限制cpu最少使用0.1个核心(1000m就代表一个核心)
        memory: 128Mi #限制内存最少占用128M
      limits:     #最多的限制
        cpu: 200m     #cpu最多受用0.2核心
        memory: 256Mi #内存最多使用256M
  restartPolicy: OnFailure #配置重启策略
3.3.1、startupProbe配置httpGet+不存在路径
kubectl create -f nginx-po.yaml

kubectl get po 

kubectl describe po nginx-po

效果如下。显然符合预期。

3.3.2、startupProbe配置httpGet+存在的路径

将其中的path的值换成存在的"index.html",如下:

    startupProbe: #应用启动探针配置
      httpGet: #探针方式,基于http请求探针
        path: index.html #http请求路径
        port: 80 #请求的端口
      failureThreshold: 3 #失败多少次算真正失败
      periodSeconds: 10 #间隔时间
      successThreshold: 1 #多少次监控算成功
      timeoutSeconds: 5 #请求的超时时间

kubectl get po 
kubectl delete po nginx-po
kubectl create -f nginx-po.yaml
kubectl get po
kubectl describe po nginx-po

验证效果如下。

3.3.3、startupProbe配置tcpSocket+80端口

启动探针换成如下:

    startupProbe: #应用启动探针配置
      tcpSocket:
        port: 80 #请求的端口
      failureThreshold: 3 #失败多少次算真正失败
      periodSeconds: 10 #间隔时间
      successThreshold: 1 #多少次监控算成功
      timeoutSeconds: 5 #请求的超时时间

验证效果:

kubectl get po 
kubectl delete po nginx-po
kubectl create -f nginx-po.yaml
kubectl get po
kubectl describe po nginx-po

成功起来了,符合预期。

3.3.4、startupProbe配置执行命令的方式

将启动探针配置如下。含义是执行指令"sleep 2; echo success > /inited"

    startupProbe: #应用启动探针配置
      exec:
        command:
        - sh
        - -c
        - "sleep 2; echo success > /inited"
      failureThreshold: 3 #失败多少次算真正失败
      periodSeconds: 10 #间隔时间
      successThreshold: 1 #多少次监控算成功
      timeoutSeconds: 5 #请求的超时时间


#重新启动一个容器
kubectl delete po nginx-po
kubectl create -f nginx-po.yaml
kubectl get po
kubectl describe po nginx-po


#接下来进入pod看看inited文件是否符合预期
kubectl exec -it nginx-po  -- /bin/bash

#输出inited文件
cat /inited

经验证服务预期。

3.3.5、验证存活探针

总共配置如下两个探针。 另外在宿主机当前工作目录准备 test.html 空文件。

    startupProbe: #应用启动探针配置
      exec:
        command:
        - sh
        - -c
        - "sleep 2; echo success > /inited"
      failureThreshold: 3 #失败多少次算真正失败
      periodSeconds: 10 #间隔时间
      successThreshold: 1 #多少次监控算成功
      timeoutSeconds: 5 #请求的超时时间
    livenessProbe: #应用存活探针配置
      httpGet: 
        path: /test.html
        port: 80
      failureThreshold: 3 #失败多少次算真正失败
      periodSeconds: 10 #间隔时间
      successThreshold: 1 #多少次监控算成功
      timeoutSeconds: 5 #请求的超时时间

#前期可以看到并不成功
kubectl create -f nginx-liveness.yaml
kubectl get po
kubectl describe po nginx-po

#执行如下指令后在观察就成功了。
kubectl cp test.html nginx-po:/usr/share/nginx/html/

3.4、生命周期

整个生命周期的过程大致如下。

其中:preStop和postStart是容器的生命周期钩子,它们可以在容器终止之前或容器启动之后执行特定的操作。

3.3.1、退出流程

(1)Endpoint删除pod的ip地址。

(2)Pod变成Terminating状态。

变成删除的状态后还会给pod一个宽限期,让pod去执行一些清理或销毁的操作。

通过如下参数设置宽限期。

terminationGracePeriodSeconds: 30

(3)执行Prestop的指令。

3.3.2、PreStop的应用

注册心中下线、数据清理、数据销毁等场景需要结束preStop。

四、资源调度

1、pod本身存在很多不便利性。例如说想对pod扩容会发现扩不了,想去编辑pod对应的yaml文件也更改不了(kubectl edit),只能删除后再构建。
2、其实我们一般很少直接操作pod,更多地我们是通过k8s提供的一系列控制器实现对pod的操作的。总结来说:

在kubernetes中,Pod是最小的控制单元,但是kubernetes很少直接控制Pod一般都是通过Pod控制器来完成的。Pod控制器用于pod的管理,确保pod资源符合预期的状态;当pod的资源出现故障时,会尝试进行重启或重建pod。在kubernetes中Pod控制器的种类有很多,接下来重点介绍:Deployment(适用于有状态服务)和StatefulSet(适用于无状态服务)。

4.0、Label和Selector

Label是kubernetes系统中的一个重要概念,先介绍下这个。

**它的作用就是在资源上添加标识,用来对它们进行区分和选择。**Label的特点如下:

  • 一个Label会以key/value键值对的形式附加到各种对象上,如Node、Pod、Service等等
  • 一个资源对象可以定义任意数量的Label ,同一个Label也可以被添加到任意数量的资源对象上去
  • Label通常在资源对象定义时确定,当然也可以在对象创建后动态添加或者删除
  • 可以通过Label实现资源的多维度分组,以便灵活、方便地进行资源分配、调度、配置、部署等管理工作。

一些常用的Label 示例如下:

版本标签:"version":"release", "version":"stable"...

环境标签:"environment":"dev","environment":"test","environment":"pro"

架构标签:"tier":"frontend","tier":"backend"

标签定义完毕之后,还要考虑到标签的选择,这就要使用到Label Selector,进行选择即:

Label用于给某个资源对象定义标识!!

Label Selector用于查询和筛选拥有某些标签的资源对象(选择器)!!

当前有两种Label Selector:

(1)基于等式的Label Selector

name = slave: 选择所有包含Label中key="name"且value="slave"的对象

env != production: 选择所有包括Label中的key="env"且value不等于"production"的对象

(2)基于集合的Label Selector

name in (master, slave): 选择所有包含Label中的key="name"且value="master"或"slave"的对象

name not in (frontend): 选择所有包含Label中的key="name"且value不等于"frontend"的对象

标签的选择条件可以使用多个,此时将多个Label Selector进行组合,使用逗号","进行分隔即可。例如:

name=slave,env!=production

name not in (frontend),env!=production

k8s就是通过这种方式实现对象与对象之间的关联!!!

4.0.1、Label的添加、修改与查看

(1)通过配置文件的方式添加labels

(2)通过命令行的方式

#注意:下面都是以pod为例,所以 "kubectl label po <podname>",其他资源也是可以的。

1) 创建临时标签
kubectl label po <资源名称> app=appname -n <namespace>

2)修改已经存在的标签
kubectl label po <资源名称> app=appname2 -n <namespace> --overwrite

3)查看标签
kubectl get po --show-labels   #default命名空间
kubectl get po  --show-labels -n kube-system   #执行命名空间

4)一把查看很多资源的label
kubectl get po,rs,deploy --show-labels


#创建一个pod
kubectl create -f nginx-po.yaml
#查看这个pod的默认的标签
kubectl get po --show-labels

#给nginx-po添加标签"author=zszs"
kubectl label po nginx-po author=zszs 

#再次查看标签果然就有了
kubectl get po --show-labels

#删除author标签
kubectl label po nginx-po author-

果然,成功添加了标签。

4.0.2、Selector的使用

一般通过配置文件的 spec.selector 或其他可以写selector的属性中描述选择器信息。

我们下面通过命令行的方式体验下Selector的效果。

#匹配单个值
kubectl get po -A -l author=zszs
kubectl get po -A -l author=zszs  --show-labels


#匹配多个值
kubectl get po -A -l 'author in (zszs)'

#多个条件的查询(条件之间是"逻辑与"的关系,即所有条件都要满足才行)
kubectl get po -A -l author=zszs,type=app,test!=1.0.1
kubectl get po -A -l 'author=zszs,type=app,test in (1.0.0)'

4.1、Deployment

4.1.1、创建
#先创建一个deploy
kubectl create deploy nginx-deploy --image=nginx:1.7.9

#注:可以看到deploy里面嵌套replicaset,rs里面又嵌套了po。
kubectl get deploy 
kubectl get replicaset
kubectl get po 

#注:也可以一把get很多资源
kubectl get po,rs,deploy

可以看到现有deploy、deploy里面有replicaset、rs里面又有pod,这个层级关系和前面概念介绍完全一致!!!!!

4.1.2、滚动更新

只有修改 deployment 配置文件中的 template 中的属性后才会触发更新操作。

#首先将副本数改成3(将其中的 spec.replicas 改为3)
kubectl edit deploy nginx-deploy 

#验证确实变成三个了
kubectl get deploy
kubectl get po

#尝试修改label中的信息,发现并不会更新;

#尝试修改template中的信息,例如更改nginx的版本
kubectl get deploy

#产看滚动更新的记录(可以看到滚动更新已经完成的提示) 注: -w表示监控这个指标
kubectl rollout status deploy nginx-deploy -w 

#我们也可以通过set的方式更改单个指标
(修改deployment/nginx-deploy这个deploy中的name为nginx的这个容器的image字段值为nginx:1.7.9)
kubectl set image deployment/nginx-deploy nginx=nginx:1.7.9


#当然我们也可以通过describe的方式查看更新记录
kubectl describe deploy nginx-deploy
#另外我们还可以查看下rs
kubectl get rs

如下图所示我们可以看到有两个rs。这个就和入门篇讲到的滚动更新流程关联起来了。即滚动更新的机制是先创建一个新的rs,新rs每启动一个容器,就会在旧rs里面销毁一个,直到完成所有替换。同理,如果我们再次修改template的信息就会有再增加一个rs。注意,这里修改template的啥信息都行,不一定非的是镜像版本;因为修改任何信息都是一个新的"版本"。

另外,我们还可以看到这两个rs依赖的pod-template是不一样的。

4.1.3、回滚
#查看之前的发布修订版本和配置
kubectl rollout history deployment/nginx-deploy

#查看某个版本的详细信息
kubectl rollout history deployment/nginx-deploy --revision=2

#确认回退版本后可以通过kubectl rollout undo实施
kubectl rollout undo deployment/nginx-deploy --to-revision=2

#查看效果
kubectl get rs
kubectl get po

注意:之所以能回退是因为k8s帮我们记录了相应版本的rs。至于记录多少个版本的rs是由参数 spec.revisionHistoryLimit 所指定的。

4.1.4、扩缩容

方式一:kubectl edit

kubectl edit 修改其中的 spec.replicas 参数;只不过这样比较麻烦,为此k8s提供了专门的指令。

方式二:kubectl scale

kubectl get rs
kubectl scale --help
kubectl scale --replicas=6 deploy nginx-deploy
#查看效果
kubectl get deploy

#缩容
kubectl scale --replicas=3 deploy nginx-deploy
kubectl get deploy
kubectl get rs

注:这种方式只是基于同一套模板创建了新的副本,rs的数量是不会变化的。

4.1.5、暂停与恢复

有的时候我们需要频繁的修改template,我们知道每次修改template都会触发更新deployment的操作。显然我们只希望执行最后一次更新即可,中间都是没意义的。对于这种场景我们可以先把 deployment 给停了,最后需要的时候再开启即可。

#kubectl rollout pause实现暂停,直到恢复后才会继续更新。
kubectl rollout pause deployment <deploy_name> 

#通过如下指令恢复
kubectl rollout deploy <deploy_name>

我们在 template.spec.containers 下添加如下配置。

resources:

requests:

cpu: 100m

memory: 128Mi

kubectl rollout pause deployment nginx-deploy
kubectl rollout resume deploy nginx-deploy

4.2、StatefulSet

4.2.1、创建

首先创建名为web.yaml内容如下的yaml文件。

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet   #StatefulSet类型的资源
metadata:
  name: web  #statefulset对象的名字
spec:
  serviceName: "nginx" #使用哪个 service 来管理dns(就是最前面的那小段yaml)
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx 
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:    #容器内部要暴露的端口
        - containerPort: 80   #暴露的端口号
          name: web  #端口配置的名字

#删除之前的deploy(避免重名)
kubectl delete deploy nginx-deploy

#这里创建一个service和一个statefulset
kubectl create -f web.yaml

kubectl get po

接下来验证pod的名字 web-0/web-1 是否能够访问。

注:在宿主机上直接访问肯定是不行的,因为宿主机并不在集群内部;我们可以通过额外创建一个容器,在此容器里面看看能否直接通过名字访问。

#busybox:理解为linux里面的一个工具镜像,一般用于测试工作。 
kubectl run -it --image busybox dns-test --restart=Never --rm /bin/sh

#如下ping操作发现是能通的
ping web-0.nginx
ping web-1.nginx

注:web-0是pod的名字;nginx是pod里面服务的名字。

如下,是能通的。

至此,就实现了域名访问。而且两个的ip地址也是不一样的。

4.2.2、扩容缩容

两种方式。1) kubectl scale 的方式;2)kubectl patch的方式。

#操作前观察pod/sts
kubectl get po
kubectl get sts

#扩容(kubectl scale方式)
kubectl scale statefulset web --replicas=5

#缩容(kubectl patch方式) 以json的形式指定配置属性
kubectl patch statefulset web -p '{"spec":{"replicas":3}}'

#查看描述(操作记录)
kubectl describe sts web

这里可以注意到是有顺序性的。例如扩容的时候扩的是 web-2 → web-3 → web-4;缩容的时候web-4 → web-3。

4.2.3、镜像更新

kubectl patch 表示打补丁的操作。

注意:对于statefulset目前并不支持直接更新 image,需要使用patch(打补丁)的方式来实现。

#解读一下:这里要对名为web的statefulset资源进行打补丁;通过json形式描述;
#json中:要进行的操作是替换replace;把 "/spec/template/spec/containers/0/image" 路径下替换成的值是"nginx:1.9.1"。
kubectl patch sts web --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/image","value":"nginx:1.9.1"}]'


#查看是否正在进行更新
kubectl get sts

#查看之前的发布修订版本和配置
kubectl rollout history sts web

#查看具体版本的修改情况
kubectl rollout history sts web --revision=2

#查看更新情况(会专门输出更新情况)
kubectl rollout status sts web

#通过edit直接查看pod的yaml配置文件(可以看到nginx的版本确实更新了)
kubectl get po
kubectl edit po web-0

1)普通滚动更新 vs 修改partition的滚动更新(可以实现灰度发布效果)

①普通滚动更新的策略是滚动替换但是会替换所有。

② 灰度发布(金丝雀)发布的要以是先更新少部分服务器(影响降到最低);确认没问题后在滚动更新发布剩余所有。其实

利用滚动更新中的 partition 属性,可以实现简易的灰度发布的效果

例如我们有 5 个 pod,如果当前 partition 设置为 3,那么此时滚动更新时,只会更新那些 序号 >= 3 的 pod。利用该机制我们可以通过控制 partition 的值,来决定只更新其中一部分 pod,确认没有问题后再主键增大更新的 pod 数量,最终实现全部 pod 更新。

金丝雀发布名称由来: 以前旷工开矿下矿洞前,先会放一只金丝雀进去探是否有有毒气体,看金丝雀能否活下来,金丝雀发布由此得名。

#首先将副本数扩充至5
kubectl scale statefulset web --replicas=5

#可以看到web-0~web-4都是一个版本
kubectl edit po web-4

#接下来修改web这个sts描述文件的信息,实现部分更新。
##①将nginx镜像换成"nginx:1.7.9" ②将 updateStrategy.rollingUpdate.partition 的值改成3(预期web-4/web-3会被更新,其他几个不更新)。
kubectl edit sts web


#验证查看web-4/web-3/web-2版本,可以看到大于等于3的都更新了,其他不更新;符合预期。
kubectl edit po web-3
kubectl edit po web-2

#如果观察没问题想要继续升级更多机器,同样的方法调小partition即可(验证查看符合预期)。

2)OnDelete 更新。

按照前面所说,只要更改模板就是给我们更新。

但是当我们将更新模式变成 onDelete 后,上面的规则就会失效。顾名思义,其实际效果是只有当pod被删除的时候才会更新。

#编辑web这个sts的配置文件
##将更新策略改成如下:
  updateStrategy:
    type: OnDelete
kubectl edit sts web


#然后在修改版本发现并不会更新


#然后删除pod发现重建后就会更新
kubectl delete po web-4
4.2.4、删除

注:statefulset直接管理到pod,没有rs;rs的概念是deployment里面才有的。

另外,需要知道有状态服务的删除和无状态服务的删除肯定是不一样的。对于deployment我们删除deploy的时候,里面rs/pod等都是会自动删除; 但是对于statefulset有专门做区分。

删除statefulset涉及多个东西

kubectl get po

kubectl get svc

kubectl get sts

kubectl get pvc #可能还会有pvc

1)级联删除

#删除 statefulset 时会同时删除 pods

kubectl delete statefulset web

2)非级联删除

删除 statefulset 时不会删除 pods,删除 sts 后,pods 就没人管了,此时再删除 pod 不会重建的

kubectl deelte sts web --cascade=false

删除 service

kubectl delete service nginx

4.3、DaemonSet

守护进程,一般用于监控、日志收集、清理。

对于日志收集我们假设如下场景。我们有3个node(node1~3)部署了6个电商相关的微服务,电商相关的node会有一个统一的Label(type:microservice);另外,要知道微服务彼此之间有调用关系。 对于这种场景,如果出问题了要怎么查询日志?

1、原始版本(日志分散各处) 。每个微服务都搞一个数据卷持久化到宿主机,我们查完node1的日志再查node2的日志,如此反复。 显然,这种日志不集中的方式太蠢了。
2、日志集中存储 。为此有必要在每个node上都部署一个用于收集日志的进程(例如Fluentd,其作用就是收集日志并发送给ES)。所有node部署的Fluentd会将收集的日志统一上报到一个全新的部署elasticsearch的node上。显然,这样就实现了日志统一管理。

但是又有一个新的问题,那就是每个node分别去部署守护进程太麻烦了、而且如果每次扩容都要认为参与的话显然不可接受。
3、DaemonSet。DaemonSet内部会有个nodeSelector(顾名思义就是选择节点的),我们只需要配置好选择器选择的标签是什么(例如microservice)daemonset就会自动给具备该标签的node部署一个Fluentd。而且对于扩容场景也无需认为干预,自动完成。显然,这就是我们想要的。

4.3.1、配置文件

(1)创建

创建名为 fluentd-ds.yaml 内容如下的配置文件。

apiVersion: apps/v1
kind: DaemonSet   #创建Daemonset类型的资源
metadata:
  name: fluentd
spec:
  template:
    metadata:
      labels:
        app: logging
        id: fluentd
      name: fluentd  
    spec:
      containers:
      - name: fluentd-es
        image: agilestacks/fluentd-elasticsearch:v1.3.0
        env:   #换名变量配置
         - name: FLUENTD_ARGS  #环境变量的key
           value: -qq          #环境变量的value
        volumeMounts:   #加载数据卷避免丢失
         - name: containers
           mountPath: /var/lib/docker/containers  #将下面的主机的路径加载到容器的此处的路径
         - name: varlog
           mountPath: /varlog
      volumes:  #定义数据卷
         - hostPath:  #数据卷类型,主机路径的模式
             path: /var/lib/docker/containers    #宿主机的此路径 和上面的容器的"/var/lib/docker/containers"路径映射
           name: containers
         - hostPath:
             path: /var/log   #宿主机的此路径 和上面的容器的"/varlog"路径映射
           name: varlog

可以看到我们并没有在配置文件中指定这个daemonset部署到哪些node(没有指定nodeSelector)。

4.3.2、指定Node节点

DaemonSet指定Node节点有三种方式,分别是: ①nodeSelector(节点选择器) ②nodeAffinity(节点亲和力) ③podAffinity(pod亲和力)。 此处暂且只介绍nodeSelector,后两者以后介绍。

(1)nodeSelector

1)先看看没有指定nodeSelector的默认情况如何。

#创建这个daemonset
kubectl create -f fluentd-ds.yaml

#查看这个daemonset
kubectl get ds


#可以看到默认情况下是给我们部署了所有非master的节点(此时配置文件中并没有指定nodeSelector)。
kubectl get po
kubectl get po -o wide
kubectl get nodes
kubectl get nodes --show-labels

结论:可以看到默认情况下是给我们部署了所有非master的节点(此时配置文件中并没有指定nodeSelector)。

2)接下来给ds添加nodeSelector

#给node1添加标签
kubectl label no k8s-node1 type=microservices

#node1上就有了这个标签
kubectl get nodes --show-labels

#接下来编辑这个ds,添加一个nodeSelector(后续yaml文件主要是nodeSelector部分)
kubectl edit ds fluentd

#观察效果(发现此时就只有一个了)
kubectl get ds

#可以看到这个fluentd的情况
kubectl get po -l app=logging -o wide

#接下里我们给node2也添加同样的标签
kubectl label no vm-32-7-opencloudos type=microservices

#可以看到第二个node也被添加了ds这个pod
kubectl get po -l app=logging -o wide

在spec.containers同级目录下:

可以看到此时就只部署k8s-node1节点。符合预期。

即,ds自己会去监听各个节点的标签情况,一旦发现条件符合就是自动部署相应的ds上去。

有了这个时候,我们对于守护进程的管理就非常方便了。

4.3.3、滚动更新

对于daemonset支持的更新方式和之前的statefulset一样。

不过对于daemonset一般建议采用Ondelete的方式更好,需要更新谁就删除谁;原因是部署daemonset的节点可能非常非常多,所有的都频繁更新资源消耗过大。

4.4、HPA自动扩/缩容

HPA(Horizontal Pod autoscaler)含义为水平自动扩缩容。

就是可以根据CPU使用率、内存使用率或其他自定义指标(metrics)自动地对pod进行扩缩容。

注:通常用于 Deployment、StatefulSet,不适用于无法扩/缩容的对象,如 DaemonSet

(1)控制管理器每隔30s(可以通过--horizontal-pod-autoscaler-sync-period修改)查询metrics的资源使用情况

(2)支持多种指标类型:

1)预定义指标(metrics)(比如Pod的CPU、内存)以利用率的方式计算

2)自定义的Pod metrics,以原始值(raw-value)的方式计算

3)自定义的object metrics(需要把自定义的api注册上去相对麻烦)

4.4.1、安装 metrics-server(指标服务)

metrics-server是kubernetes监控体系中的核心组件之一,它负责从kubelet收集资源指标然后对这些指标监控数据进行聚合(依赖 kube-aggregator),并在kubernetes apiserver中通过Metrics API 公开暴露他们。

对于我们,不安装的话执行"kubectl top pod"会出现"error: Metrics API not availabel"报错。

(1)下载 metrics-server 组件配置文件
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -O metrics-server-components.yaml

(2)修改镜像地址为国内的地址
sed -i 's/registry.k8s.io\/metrics-server/registry.cn-hangzhou.aliyuncs.com\/google_containers/g' metrics-server-components.yaml

注意:上述执行要替换的部分可能需要自己调整,替换后执行如下指令看看镜像是否真的替换过来就好了。
grep image metrics-server-components.yaml

(3)修改容器的 tls 配置,不验证 tls,在 containers 的 args 参数中增加 --kubelet-insecure-tls 参数(添加一行"- --kubelet-insecure-tls")

(4)安装组件
kubectl apply -f metrics-server-components.yaml

(5)查看 pod 状态
kubectl get pods --all-namespaces | grep metrics

(6)然后就可以看到各pod的监控指标了
kubectl top pod

效果如下:

4.4.2、创建nginx-deploy用于验证效果

实现 cpu 或内存的监控,首先有个前提条件是该对象必须配置了 resources.requests.cpu 或 resources.requests.memory 才可以,可以配置当 cpu/memory 达到上述配置的百分比后进行扩容或缩容。为此准备如下包含requests的名为nginx-deploy.yaml的配置文件。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deploy
  name: nginx-deploy
  namespace: default
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx-deploy
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: nginx-deploy
    spec:
      containers:
      - image: nginx:1.7.9
        imagePullPolicy: IfNotPresent
        name: nginx
        resources:
          limits:
            cpu: 200m
            memory:  128Mi
          requests:
            cpu: 10m
            memory: 128Mi
      restartPolicy: Always
      terminationGracePeriodSeconds: 30

#1副本创建
kubectl create -f nginx-deploy.yaml

#查看
kubectl get po 

#创建一个HPA(autoscale就是HPA),此处指定最少两个副本,最多5副本 
kubectl autoscale deploy nginx-deploy --cpu-percent=20 --min=2 --max=5

#稍等片刻在观察果然就变成两个副本了
kubectl get deploy

#获取HPA信息
kubectl get hpa

为了验证自动扩缩容,我们需要对nginx-deploy进行加压。加压可以通过如下指令死循环请求即可。

while true; do wget -q -O- http://<ip:port> > /dev/null ; done

但是这里有两个pod改怎么办呢?。

答:显然可以通过配一个service解决,统一访问svrc负载均衡就不用我们操心了。------见 下一节。

4.4.3、创建service

自己创建一个service,文件为 nginx-svc.yaml,内容如下。

目标:希望这个service能够找到前面部署的两个nginx,显然就是用选择器去选了。

为此,我们先查看这两个nginx有没有什么共有但是又区别于其他的pod的label。

kubectl get po --show-labels

注:可以看到就是"app=nginx-deploy"了,这就是我们选择器要选择的标签。

apiVersion: v1
kind: Service   #资源类型为 Service
metadata:
  name: nginx-svc  #Service的名字
  labels:
    app: nginx  #Service自己的标签(用于被别人选择)
spec:
  selector:    #匹配哪些pod会被本service代理
    app: nginx-deploy  #所有匹配到该标签的pod都可以通过service进行访问
  ports:
  - port: 80           #service自己的端口,在使用内网ip访问时使用
    targetPort: 80     #目标pod的端口(这样就相当是把上面80端口收到的数据转发到目标pod的80端口去)
    name: web          #为我们的端口取个名字(后续可能其他使用)
  type: NodePort       #总共有四种类型后面会介绍。随机启动一个端口(30000-32767)。映射到pod的端口,该端口是直接绑定到node上的且集群中的每个node都会绑定这个端口。

注:NodePort也可以用于将服务暴露给外部访问,不过这种方式实际生产环境不推荐,因为效率较低。一般我们是做测试的时候将 NodePort 暴露出来用于测试,生产环境不这么干。

#创建这个service
kubectl create -f nginx-svc.yaml

#查看这个svc(其对应的ip就是这个svc的ip)
kubectl get svc

输出如下,其中:

(1)CLUSTER-IP:就是svc的ip,我们访问这个ip即可;后面的负载均衡会自动完成。

(2)PORT:80是容器暴露端口、31194是宿主机暴露端口。

(3)如下直接访问svc的80端口或者宿主机的31194端口都是能通的,。

curl 10.107.165.131

curl 10.206.32.9:31194

五、服务发布

主要讲网络的问题。利用这两块技术实现了 ①集群内部Pod与Pod的访问 ②k8s集群之外的请求的访问。

5.1、service

5.1.1、service介绍与基础操作

还是利用上面创建的nginx-svc。

需要知道:在创建svc的时候同时还会创建一个endpoints,其含义就是这个svc实际访问的pod。

#查看service
kubectl get svc

#查看endpoints
kubectl get endpoints
kubectl get ep

#验证下nginx-svc对应的endpoints是不是就是nginx-deploy部署的两个pod
kubectl get po -l app=nginx-deploy -o wide

如下图。可以明显看到 svc 的endpoints 对应的就是被均衡的那些pod。

其流程如下:svc → endpoints → pod 。要大概理解这个图。

## service基础操作
#或许service资源
kubectl get svc

#查看svc描述信息(svc自己的ip地址/endpoints信息/nodeport等信息)
kubectl describe svc nginx-svc


## 接下来验证通过服务名访问

#进入我们的busybox
kubectl exec -it dns-test -- sh

#进入后执行(可以看到成功拉下 index.html 了)
wget http://nginx-svc

#查看html符合预期
cat index.html

##根据之前的介绍我们知道 service 是命名空间级别的资源。当然如果想跨命名空间访问也是可以的({svc_name}.{namespace})。

#即完整请求路径如下
wget http://nginx-svc.default
5.1.2、service代理k8s外部服务

前面介绍的service的主要使用场景都是k8s集群内部的访问。下面演示一个service作为正向代理访问service的案例(pod们通过service作为代理访问外界)。也是有这种诉求的:

第一种需求:不同环境本质上是配置的不同,对于服务自身来说是没有区别的。服务访问不同的配置就可以借助service来完成。

第二种需求:对于一个系统,一部分服务在k8s上另一部分在老的云服务架构上。此时同样有k8s服务需要访问外界的场景。

此时,k8s服务→service→外部服务 要优 于k8s服务→外部服务。因为,随着后续"外部服务"逐渐迁移至k8s内部,通过service访问其他服务是k8s中更自然的方式。而且,可能会有很多服务访问这个"外部服务",每个服务都去改来改去显然太麻烦了。

实现方式。编写service配置文件的时候不指定selector属性,而是自己创建endpoints。

(1)首先创建一个svc

配置文件名为 nginx-svc-external.yaml ,内容如下。

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc-external
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    targetPort: 80
    name: web
  type: ClusterIP

#创建对应svc
kubectl create -f nginx-svc-external.yaml

#查看svc
kubectl get svc

#获取ep(可以看到并没有external相关)
kubectl get ep

(2)接下来创建自己的endpoints

注:目标ip通过 ping www.wolfcode.cn 获得。

配置文件名为 nginx-ep-external.yaml, 内容如下:

apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app: nginx             # 与 service 一致(相当于绑定了前面的service)
  name: nginx-svc-external # 与 service 一致(相当于绑定了前面的service)
  namespace: default    # 与 service 一致
subsets:
- addresses:
  - ip: 120.78.159.117 # 目标 ip 地址(访问service就转发到这里)
  ports: # 与 service 一致
  - name: web  # 和 nginx-svc.yaml 一致
    port: 80
    protocol: TCP

#创建endpoints
kubectl create -f nginx-ep-external.yaml

#查看创建的ep(果然就有这个ep了)
kubectl get ep

#查看这个ep的详细信息(就可以看到响应代理信息)
kubectl describe ep nginx-svc-external

(3)接下来验证效果

注:这里就不用 busybox了,看起来有点问题。参见 https://github.com/moby/moby/issues/41348

#创建并进入 alpine
kubectl run -it --image alpine alpine-test

#进入后执行(可以看到成功拉下 index.html 了)
wget http://nginx-svc-external

#查看html符合预期
cat index.html

如下图所示。访问nginx-svc-external(svc借助endpoints)访问到 www.wolfcode.cn;相当于把service当成了正向代理。

5.1.3、service反向代理外部域名

前面虽然可以访问外部,但实际上是写死的ip地址。接下来想要通过域名进行访问。

创建名为 nginx-svc-external-domain.yaml 内容如下的配置文件:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: wolfcode-external-domain
  name: wolfcode-external-domain
spec:
  type: ExternalName
  externalName: www.wolfcode.cn

#创建这个svc
kubectl create -f nginx-svc-external-domain.yaml

#查看效果(可以看到 EXTERNAL-IP 为www.wolfcode.cn)
kubectl get svc

#同样进入 alpine 验证效果
kubectl exec -it alpine-test -- sh

#然后就直接访问到了
wget http://wolfcode-external-domain

执行效果如下:

5.1.4、service的四种常用类型

主要有四种常用类型,分别为 ClusterIP,ExternalName,NodePort,LoadBalancer。

后两者可以通过service的这种配置实现服务对外网的暴露,但是一般不推荐这么做。

(1)ClusterIP

只能在集群内部使用,不配置类型的话默认就是ClusterIP。不存在外网访问,就只是东西流量的访问。

集群内部的微服务之间的访问这个类型就足以满足日常诉求了。

(2)ExternalName

可以配置为域名

(3)NodePort

会给所有安装了 kube-proxy 的节点都绑定一个端口,此端口可以代理到对应的pod。在集群外部可以使用任意节点的ip+NodePort的端口号访问到集群中对应Pod中的服务。

当类型设置为 NodePort 后,可以在 ports 配置中增加 nodePort 配置指定端口,需要在下方的端口范围内,如果不指定会随机指定端口。端口范围:30000~32767

注: 前面有说过这种效率比较低一般不这么用。

(4)LoadBalancer

使用云服务商(阿里云、腾讯云等)提供的负载均衡器服务。

5.2、Ingress

Ingress:进入

整个k8s集群对外暴露的服务发现入口。

从角色上来讲Ingress就可以理解为传统架构的Nginx。更具体地,Ingress是基于Nginx抽象出来的一个概念(更标准/更通用)。见如下对比示意图。需要知道 Ingress是抽象出来的一个概念层,至于选择何种实现都是可以的,例如nginx就是其实现之一。注:有点类似于 container runtime 与 docker的关系。

5.2.1、Ingress的安装

接下来演示通过Helm安装 Ingress-nginx。

Ingress-nginx安装指南。 https://kubernetes.github.io/ingress-nginx/deploy/#using-helm

1、首先安装Helm

介绍:就是个包管理器。理解为java的maven、linux的yum就好。

安装方法也可参见官网: https://helm.sh/docs/intro/install

#下载安装包
wget https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz

#解压
tar zxvf helm-v3.2.3-linux-amd64.tar.gz

#安装(将解压目录下的helm程序移动到 /usr/local/bin/helm 即可)
cp helm /usr/local/bin/helm

#验证安装是否ok(有输出就说明ok)
helm version

2、用helm安装ingress-nginx

# 添加仓库
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

# 查看仓库列表
helm repo list

# 搜索 ingress-nginx
helm search repo ingress-nginx

# 下载下来
helm pull ingress-nginx/ingress-nginx

#解压这个下载文件
tar zxvf ingress-nginx-4.10.0.tgz

#配置参数(改动点比较多)
##修改 value.ymal

改动点如下:

镜像地址:修改为国内镜像
registry: registry.cn-hangzhou.aliyuncs.com
image: google_containers/nginx-ingress-controller
image: google_containers/kube-webhook-certgen
tag: v1.3.0

hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet

修改部署配置的 kind: DaemonSet
nodeSelector:
  ingress: "true" # 增加选择器,如果 node 上有 ingress=true 就部署
将 admissionWebhooks.enabled 修改为 false
将 service 中的 type 由 LoadBalancer 修改为 ClusterIP,如果服务器是云平台才用 LoadBalancer

#这里专门创建一个命名空间
kubectl create ns ingress-nginx

# 为需要部署 ingress 的节点上加标签
kubectl label node k8s-node1 ingress=true

注:这里需要注意默认情况下k8s不推荐将程序安装到master节点(这里尝试对master也是安装不上去的),有个"污点"的概念后面会介绍。


# 安装 ingress-nginx(在value.yaml所在目录执行)
helm install ingress-nginx -n ingress-nginx .

注: 期间出现了一个报错,按照这个操作即可 https://blog.csdn.net/qq_65380630/article/details/135620045

# 验证安装是否成功(有pod就说明安装成功了)
kubectl get po -n ingress-nginx

注:这里需要注意默认情况下k8s不推荐将程序安装到master节点(这里尝试对master也是安装不上去的),有个"污点"的概念后面会介绍。

如下图所示pod正常起来应该就说明ok了!!

5.2.2、Ingress的基本使用

创建一个名为 zs-ingress.yaml 内容如下的文件:

apiVersion: networking.k8s.io/v1
kind: Ingress # 资源类型为 Ingress
metadata:
  name: zs-nginx-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:    # 下面的这些内容就和nginx对应就好
  rules: # ingress 规则配置,可以配置多个
  - host: k8s.zszs.cn # 域名配置,可以使用通配符 *
    http:
      paths: # 相当于 nginx 的 location 配置,可以配置多个
      - pathType: Prefix # 路径类型,按照路径类型进行匹配 ImplementationSpecific 需要指定 IngressClass,具体匹配规则以 IngressClass 中的规则为准。Exact:精确匹配,URL需要与path完全匹配上,且区分大小写的。Prefix:以 / 作为分隔符来进行前缀匹配
        backend:
          service: 
            name: nginx-svc # 代理到哪个 service
            port: 
              number: 80 # service 的端口
        path: /api # 等价于 nginx 中的 location 的路径前缀匹配

#创建ingress
kubectl create -f zs-ingress.yaml


#查看ingress
kubectl get ingress

#ingress controller所在pod的信息(此处安装在k8s-node1上)
kubectl get po -n ingress-nginx -o wide 

#注意:此时其效果就相当于是在k8s-node1这台机上启动了一个nginx。直接访问master之类的是不通的。

#监听日志,看看请求是否真的进来了
kubectl logs -f 

接下来验证效果

#window机器配置hosts文件,添加如下一行 node1的外网ip与域名的映射
#hosts文件路径为 C:\Windows\System32\drivers\etc
175.27.156.69 k8s.zszs.cn

#然后打开浏览器访问如下链接(发现是能通的)
k8s.zszs.cn/api

效果如下:

6、配置管理

6.1、ConfigMap

就理解为明文的键值对,明文的、大家都可以看得到的。

(1)创建configmap

很简单,就直接用命令创建就好

#查看帮助信息
kubectl create configmap -h

第一种:基于文件夹的方式(test文件夹下有db.properties/redis.properties两个文件 )

#基于文件夹创建(用的也挺多)
kubectl create configmap test-dir-config --from-file=test/

#查看configmap
kubectl get cm

#查看详细描述信息
kubectl describe cm test-dir-config

#编辑configmap(此处对test-dir-config进行编辑)
kubectl edit cm test-dir-config

第二种:基于指定文件的方式创建(把某几个文件放到configmap中去)。

#对于如下demo,其含义就是讲file1.txt和file2.txt放到configmap中。其中key1、key2就是对两个文件的重命令。
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt

第三种:直接写Key/Value的方式

kubectl create configmap test-env-config --from-literal=JAVA_OPTS_TEST='-Xms512m -Xm512m' --from-literal=APP_NAME='my_test_svr'


kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2

(2)使用configmap(加载到pod中)

创建名为 file-test-pod.yaml的配置文件,内容如下。

apiVersion: v1
kind: Pod
metadata:
  name: test-configfile-po
spec:
  containers:
    - name: config-test
      image: alpine
      command: ["/bin/sh", "-c", "env;sleep 3600"]
      imagePullPolicy: IfNotPresent
      env:
      - name: JAVA_VM_OPTS
        valueFrom:
          configMapKeyRef:
            name: test-env-config
            key: JAVA_OPTS_TEST
      - name: APP
        valueFrom:
          configMapKeyRef:
            name: test-env-config
            key: APP_NAME
      volumeMounts:  #加载数据卷
      - name: db-config  #表示加载volumes 属性中的哪个数据卷
        mountPath: "/usr/local/mysql/conf" #想要将数据卷中的文件加载到那个目录下
        readOnly: true #是否只读
  volumes:  #数据卷挂在configmap、secret
    - name: db-config   #数据卷的名字 随意设置
      configMap: #数据卷类型为ConfigMap
        name: test-dir-config #configmap的名字,想要加载哪个configmap就填哪个的名字(kubectl get cm)
        items:  #对于configmap中的key进行映射;如果不指定默认会将configmap中所有的key全部转换为同名文件
        - key: "db.properties"  #configmap中的key
          path: "db.properties"  #将该key的值转换为文件
  restartPolicy: Never

这里主要验证两个功能。

(1)将configmap中的键值对加载到pod的环境变量中。

------将 JAVA_OPTS_TEST、APP_NAME两个key加载到env中作为JAVA_VM_OPTS、APP

(2)将配置文件通过configmap整体加载到容器中。

------整体的将文件加载到容器中。

如下图所示。键值对就被加载到环境变量了。

如下图所示。通过configmap将配置文件整体加载到容器指定目录了。

6.2、加密数据配置 Secret

configmap是明文的有时候我们有加密的诉求,此时用Secret。

注:这个东西用的其实也不多,这里的加密更大程度是编码(例如base64)。

在创建 Secret 时,要注意如果要加密的字符中,包含了有特殊字符,需要使用转义符转移,例如 $ 转移后为 \$,也可以对特殊字符使用单引号描述,这样就不需要转移例如 1$289*-! 转换为 '1$289*-!'

#直接指定键值对创建
kubectl create secret generic my-secret --from-literal=username=admin --from-literal=password='ds@3-/'

#查看
kubectl describe secret/my-secret

#命令行base64编解码
echo 'admin' | base64
echo 'YWRtaW4K' | base64 --decode

#查看帮助
kubectl create secret -h

#其中我们使用较多的是'kubectl create secret docker-registry'
kubectl create secret docker-registry -h

其中我们使用较多的是'kubectl create secret docker-registry',其使用步骤为。

#首先创建一个用户名、密码
kubectl create secret docker-registry XXX XXX

#yaml配置文件中配置 imagePullSecrets 
注:拉取的时候如果没有登陆就用和 containers 平齐的位置 imagePullSecrets 中配置的用户名、密码来登陆。

6.3、SubPath的使用

诉求:举个例子,对于容器中的nginx等应用程序来说都会有一个自己的配置文件,对于nginx就是 /etc/nginx/nginx.conf。我们现在想把这个配置文件以configmap的形式暴露出来,即外界修改一个nginx.conf配置文件可以作用到容器内部这么一个效果。

SubPath的作用:即将目标文件加载进来,但同时不覆盖其他文件。

根据之前关于configmap的应用,我们或许可以想到如下方法(注: 其实是不行的):

①首先从容器内部拷贝一份nginx.conf到宿主机 ②根据宿主机此配置文件创建configmap ③编辑 nginx-deploy 将这个configmap以数据卷的形式挂载到容器相应目录。

#查看pod
kubectl get po

#进入其中的一个nginx的pod
kubectl exec -it nginx-deploy-c4986b7f-f9z8q -- sh

#复制配置内容并在宿主机创建同名文件 /root/study/k8s/config/nginx.conf
cat /etc/nginx/nginx.conf

#基于宿主机的nginx.conf创建configmap,命名为nginx-conf-cm
kubectl create configmap nginx-conf-cm --from-file=./nginx.conf

#查看创建的configmap
kubectl get cm 
kubectl describe cm nginx-conf-cm


#接下来编辑deploy将configmap挂在到容器对应目录
kubectl edit deploy nginx-deploy

配置过程如下:
(1)首先在 spec.containers 同级目录添加如下数据卷(从configmap中取文件)。

      volumes:  #数据卷定义
      - name: nginx-conf  #数据卷名字
        configMap:  #数据卷类型为configMap
          name: nginx-conf-cm #依赖的configmap的名字
          items: #要将configmap中的那些数据挂载出来
          - key: nginx.conf  #指定挂载哪个key
            path: nginx.conf  #挂在后改key重命名为什么名字

(2)然后在 spec.containers 内部 进行挂载。

        volumeMounts:     #挂载数据卷
        - name: nginx-conf   #数据卷的名称
          mountPath: '/etc/nginx'   #挂载的路径

#查看pod有没有启动
kubectl get po

#查看pod的描述信息(发现启动不起来)
kubectl describe po nginx-deploy-5f9d8b6949-qsf9j

#尝试进入容器看看目录是否符合预期(会发现进去不,此时容器就没起来)
kubectl exec -it nginx-deploy-5f9d8b6949-qsf9j -- sh


容器没启动成功但是又想登陆上去看看的小tips。。。
注:有的时候容器直接就没运行起来,想进入看看情况都看不了。

在 spec.template.spec.containers 下添加如下一行即可。
        command: ["bin/sh", "-c", "nginx daemon off;sleep 3600"]

#然后就可以登录进去了(进去后发现 /etc/nginx/ 目录只剩一个nginx.conf文件,其他的全没了)
kubectl exec -it nginx-deploy-57db458cf5-fctzl -- sh

如下图所示。

分析:具体原因是加载的configmap将 /etc/nginx/ 目录整体覆盖了,人家原本除了 nginx.conf 还是有一些其他文件的。也就是说configmap挂载的逻辑为:**如果挂在目录不存在就创建,如果存在就整体覆盖。**这就是要使用 SubPath 的原因。

注:关于这个问题其实只要选择一个全新的目录不用subpath也完全ok(再配合着将这个全新目录的文件软链接到原本nginx.conf所在的目录)!!

SubPath 使用具体如下:

#编辑配置文件
kubectl edit deploy nginx-deploy

将上述 volums.configMap.items.path 的值改成 "etc/nginx/nginx.conf";
然后,在上述 volumeMounts 改成如下:
        volumeMounts:    
        - mountPath: '/etc/nginx/nginx.conf'
          name: nginx-conf
          subPath: etc/nginx/nginx.conf


#接下来pod就能正常启动且容器内 /etc/nginx/ 目录也符合预期
kubectl get po
kubectl exec -it nginx-deploy-6b789bd9b6-fmh9b -- sh

6.1.4、配置的热更新

我们通常会将项目的配置文件作为configmap然后挂在到pod,如果更新configmap中的配置会不会更新到pod中呢?分如下几种情况:

(1)默认方式:会更新,更新的周期是更新时间+缓存时间

(2)SubPath方式:不会更新

(3)键值对添加configmap方式:同样不会更新

注:对于 subPath 的方式,我们可以取消 subPath 的使用,将配置文件挂载到一个不存在的目录,避免目录的覆盖,然后再利用软连接的形式,将该文件链接到目标位置。

但是如果目标位置原本就有文件,可能无法创建软链接,此时可以基于前面讲过的 postStart 操作执行删除命令,将默认的文件删除即可

接下来验证热更新的效果。

方法一:通过edit直接修改configmap

#还是创建这个挂在db.properties文件的pod
kubectl create -f file-test-pod.yaml

#进入后可以看到 /usr/local/mysql/conf/db.properties 文件的内容
kubectl exec -it test-configfile-po -- sh

#编辑test-dir-config这个configmap
kubectl get cm
kubectl edit cm test-dir-config

#容器内不断cat db.properties可以看到大概1min后更新生效

方法二:通过replace进行替换

注:前半部分得到一个yaml;将此yaml作为后面replace操作的输入。其中"-f-"表示接受控制台的输入。

kubectl create cm test-dir-config --from-file=./test/db.properties --dry-run -oyaml | kubectl replace -f-

6.4、不可变的secret和configmap

对于一些敏感服务的配置文件,在线上有时是不允许修改的,此时在配置 configmap 时可以设置 immutable: true 来禁止修改

#在最外层添加"immutable: true"即可
kubectl edit cm test-dir-config

添加之后我们再尝试edit就会出现如下报错,提示不能更改:

7、持久化存储

7.1、Volumes(依赖本机实现)

7.1.1、HostPath

将节点上的文件或目录挂载到 Pod 上,此时该目录会变成持久化存储目录,即使 Pod 被删除后重启,也可以重新加载到该目录,该目录下的文件不会丢失

创建名为 volume/volume-test-po.yaml 文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
  name: test-volume-po
spec:
  containers:
  - image: nginx
    name: nginx-volume
    volumeMounts:
    - mountPath: /test-po # 挂载到容器的哪个目录
      name: test-volume # 挂载哪个 volume
  volumes:
  - name: test-volume
    hostPath:     #与主机共享目录,加载主机中的指定目录到容器中
      path: /data # 节点中的目录
      type: DirectoryOrCreate # 检查类型,在挂载前对挂载目录做什么检查操作,有多种选项,默认为空字符串,不做任何检查

上述 spec.volumes.hostPath.type 含义如下:

空字符串:默认类型,不做任何检查

DirectoryOrCreate:如果给定的 path 不存在,就创建一个 755 的空目录

Directory:这个目录必须存在

FileOrCreate:如果给定的文件不存在,则创建一个空文件,权限为 644

File:这个文件必须存在

Socket:UNIX 套接字,必须存在

CharDevice:字符设备,必须存在

BlockDevice:块设备,必须存在

#创建这个pod
kubectl create -f volume-test-po.yaml

#查看这个pod(可以看到落在了k8s-node1节点上)
kubectl get po -o wide

#在node1的/data/目录下创建随便touch一个文件
touch index.html

#进入 test-volume-po 查看存在相应文件
kubectl exec -it test-volume-po -- sh

#在容器内修改此文件,发现都可以同步到宿主机node1
#同理,在宿主机修改此文件也可以同步到容器中。
echo "nihaoyahahaha...." >> index.html

经验证,宿主机与容器之间可以互相同步。

7.1.2、EmptyDir

注:HostPath、NFS挂载、PV与PVC都是for持久化的,但是这个不是for持久化。它的作用纯粹就是为了方便Pod内部多个container的管理(一个pod内多个container的目录共享)。

EmptyDir 主要用于一个 Pod 中不同的 Container 共享数据使用的,由于只是在 Pod 内部使用,因此与其他 volume 比较大的区别是,当 Pod 如果被删除了,那么 emptyDir 也会被删除。

存储介质可以是任意类型,如 SSD、磁盘或网络存储。可以将 emptyDir.medium 设置为 Memory 让 k8s 使用 tmpfs(内存支持文件系统),速度比较快,但是重启 tmpfs 节点时,数据会被清除,且设置的大小会计入到 Container 的内存限制中。

创建名为 empty-dir-po.yaml 内容如下的配置文件(注:一个pod里面配置了两个容器)。

apiVersion: v1
kind: Pod
metadata:
  name: empty-dir-po
spec:
  containers:
  - image: alpine
    name: test-emptydir1
    command: ["/bin/sh", "-c", "sleep 3600;"]
    volumeMounts:
    - mountPath: /cachedir1 # 挂载到容器的哪个目录
      name: cache-volume # 挂载哪个 volume
  - image: alpine
    name: test-emptydir2
    command: ["/bin/sh", "-c", "sleep 3600;"]
    volumeMounts:
    - mountPath: /cachedir2 # 挂载到容器的哪个目录
      name: cache-volume # 挂载哪个 volume
  volumes:
  - name: cache-volume
    emptyDir: {}

#创建这个pod
kubectl create -f empty-dir-po.yaml

#查看容器是否成功启动
kubectl get po

#进入Pod的第一个容器(-c指定container的名字)
kubectl exec -it empty-dir-po -c test-emptydir1 -- sh

#进入Pod的第二个容器(-c指定container的名字)
kubectl exec -it empty-dir-po -c test-emptydir2 -- sh

经验证,容器1挂在目录cachedir1 与 容器2挂在目录cachedir2 文件可以共享

7.2、NFS挂载(依赖远端实现)

7.2.1、nfs介绍

NFS(Network File System)顾名思义网络文件系统,就理解为远端的(专门用于存储的机器)上的文件系统。功能就是通过网络,让不同的机器、不同的操作系统可以共享彼此的文件。
注:NFS本身是一项非常通用的技术,和k8s没啥关系。 只不过这里k8s可以通过nfs卷利用NFS的功能。 参见:https://mp.csdn.net/mp_blog/creation/editor/130904497

nfs卷可以实现将NFS(网络文件系统)挂在到Pod中。不像emptyDir那样在删除Pod的时候回被删除,nfs卷的内容因为保存在远端在删除Pod的时候仍然会被保存,卸载的只是卷。

显然nfs在用于数据持久化的同时还可以用于Pod间的数据共享(相当于同时具备了HostPath和emptyDir两者的优点)。缺点:就是要经过网络(即便是内网),稳定性和效率有折扣。

7.2.2、nfs安装与基本功能使用(跨节点的文件共享)
# 安装 nfs
yum install nfs-utils -y

# 启动 nfs
systemctl start nfs-server

# 查看 nfs 版本
cat /proc/fs/nfsd/versions

# 创建共享目录(node1上操作)
mkdir -p /data/nfs
cd /data/nfs
mkdir rw
mkdir ro

# 设置共享目录 export (配置了只要是10.206.32网段都可以访问当前目录 注:master节点内网ip所在网段)
vim /etc/exports
/data/nfs/rw 10.206.32.0/24(rw,sync,no_subtree_check,no_root_squash)
/data/nfs/ro 10.206.32.0/24(ro,sync,no_subtree_check,no_root_squash)

# 重新加载
exportfs -f
systemctl reload nfs-server

# 到其他测试节点安装 nfs-utils 并加载测试(master节点上操作) 
# 注: 下面ip就是node1机器的内网ip
mkdir -p /mnt/nfs/rw
mount -t nfs 10.206.0.xx:/data/nfs/rw /mnt/nfs/rw

mkdir -p /mnt/nfs/ro
mount -t nfs 10.206.0.xx:/data/nfs/r0 /mnt/nfs/ro

#验证效果
node1机器/data/nfs/rw目录创建的文件可以在master机器的/mnt/nfs/rw目录看到;且相互同步。

显然,可以利用NFS实现文件跨机器的同步!!!!

7.2.3、nfs服务挂载到容器

将node1机器上的 /data/nfs/rw 路径通过nfs卷挂在到nginx容器的 /usr/share/nginx/html目录。预期实现的效果是,修改node1的 /data/nfs/rw/index.html 内容就可以影响到 curl pod_ip的输出。

#首先在node1的机器上创建目录和文件
echo "<h1>wolcome to zs<h1>" >> /data/nfs/rw/index.html


创建文件名为 nfs-test-po.yaml 内容如下的配置文件。

apiVersion: v1
kind: Pod
metadata:
  name: nfs-test-po1
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /usr/share/nginx/html  #覆盖nginx存放html的目录
      name: test-volume
  volumes:
  - name: test-volume
    nfs:
      server: 10.206.0.10 # 网络存储服务地址
      path: /data/nfs/rw # 网络存储路径
      readOnly: false # 是否只读


#创建
kubectl create -f nfs-test-po.yaml

#查看ip
kubectl get po -o wide

#如果服务因为挂载不上导致起不了。把共享目标权限改为*(所有、全部),然后在重新create下。
vim /etc/exports
/data/nfs/rw *(rw,sync,no_subtree_check,no_root_squash)
exportfs -f
systemctl reload nfs-server

如下:修改node1机器上的/data/nfs/rw/index.html文件,curl pod_ip 的结果也随之变化。显然实现了NFS在容器中的共享。

更进一步,我们更改上述配置文件将podname改成 nfs-test-po2,再create一个pod出来。

对于nfs-test-po2来说同样可以共享node1机器上的nfs文件。

7.3、PV与PVC

7.3.1、PV与PVC的概念

持久卷 | Kubernetes

磁盘、NFS、iSCSI是负责具体存储的方案,这里把他们统一当成具备存储能力的资源;为了屏蔽不同资源的是实现细节,引入了PV和PVC的概念。就理解成一个标准化的抽象起到规范的作用,至于背后究竟是什么来存储不重要。不同的pod都有自己数据存储诉求,不同pod对于存储资源的诉求就是理解成PVC。PV是封装了具体的存储制备的细节,其实际依赖的可能是磁盘、NFS等,PV就是对这些的统一封装。

举个例子把资源的提供与消费串起来:磁盘/NFS这些相当于是真正提供资源(产品)的厂家、PV相当于是代理商(商家)、PVC就理解为代购、Pod(微服务)就是最终的消费者。

7.3.2、PV的生命周期

(1)构建

静态构建 与 动态构建

1)静态构建:集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息, 并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。

2)动态构建:如果集群中已经有的 PV 无法满足 PVC 的需求,那么集群会根据 PVC 自动构建一个 PV,该操作是通过 StorageClass 实现的。想要实现这个操作,前提是 PVC 必须设置 StorageClass,否则会无法动态构建该 PV,可以通过启用 DefaultStorageClass 来实现 PV 的构建。

(2)绑定

当用户创建一个 PVC 对象后,主节点会监测新的 PVC 对象,并且寻找与之匹配的 PV 卷,找到 PV 卷后将二者绑定在一起。

如果找不到对应的 PV,则需要看 PVC 是否设置 StorageClass 来决定是否动态创建 PV,若没有配置,PVC 就会一致处于未绑定状态,直到有与之匹配的 PV 后才会申领绑定关系。

(3)使用

Pod 将 PVC 当作存储卷来使用,集群会通过 PVC 找到绑定的 PV,并为 Pod 挂载该卷。

Pod 一旦使用 PVC 绑定 PV 后,为了保护数据,避免数据丢失问题,PV 对象会受到保护,在系统中无法被删除。

(4)回收策略

保留(Retain)、删除(Delete)、回收(Recycle).

1)回收策略 Retain: 使得用户可以手动回收资源。当 PersistentVolumeClaim 对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。 由于卷上仍然存在这前一申领人的数据,该卷还不能用于其他申领。 管理员可以通过下面的步骤来手动回收该卷:

删除 PersistentVolume 对象。与之相关的、位于外部基础设施中的存储资产 (例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 删除之后仍然存在。

根据情况,手动清除所关联的存储资产上的数据。

手动删除所关联的存储资产。

如果你希望重用该存储资产,可以基于存储资产的定义创建新的 PersistentVolume 卷对象。

2)删除(Delete): 对于支持 Delete 回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 动态制备的卷会继承其 StorageClass 中设置的回收策略, 该策略默认为 Delete。管理员需要根据用户的期望来配置 StorageClass; 否则 PV 卷被创建之后必须要被编辑或者修补。

3)回收(Recycle):

警告: 回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。

7.3.3、PV的使用

准备:在node1机器的 /data/nfs/rw/ 路径下创建 test-pv 文件夹。

创建名为 pv-nfs.yaml 内容如下的配置文件。

apiVersion: v1
kind: PersistentVolume  #资源对象是PV类型
metadata:
  name: pv0001
spec:
  capacity:
    storage: 5Gi # pv 的容量
  volumeMode: Filesystem # 存储类型为文件系统
  accessModes: # 访问模式:ReadWriteOnce、ReadWriteMany、ReadOnlyMany
    - ReadWriteOnce # 可被单节点独写
  persistentVolumeReclaimPolicy: Recycle # 回收策略
  storageClassName: slow # 创建 PV 的存储类名,需要与 pvc 的相同
  mountOptions: # 加载配置
    - hard
    - nfsvers=4.1
  nfs: # 连接到 nfs
    path: /data/nfs/rw/test-pv # 存储路径
    server: 10.206.0.10 # nfs 服务地址

#创建pv
kubectl create -f pv-nfs.yaml

#查看pv
kubectl get pv
kubectl describe pv pv0001

留意下其中有个STATUS字段,取值及含义如下:

Avaliable: 空闲,违背绑定

Bound: 已经被PVC绑定

Released: PVC被删除,资源已回收,但是PV未被重新使用

Failed: 自动回收失败

7.3.4、PVC的使用

一般都是先搞一个Pod然后关联一个PVC,当然我们也可以单独创建一个PVC(此处先创建pvc)。

创建名为 pvc-test.yaml ,内容如下的配置文件。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteOnce # 权限需要与对应的 pv 相同
  volumeMode: Filesystem
  resources:
    requests:
      storage: 4Gi # 资源可以小于 pv 的,但是不能大于,如果大于就会匹配不到 pv
  storageClassName: slow # 名字需要与对应的 pv 相同
#  selector: # 使用选择器选择对应的 pv (通过选择器可以让pvc更细致地选择pv)
#    matchLabels:
#      release: "stable"
#    matchExpressions:
#      - {key: environment, operator: In, values: [dev]}

#创建这个pvc
kubectl create -f pvc-test.yaml

#查看pvc
kubectl get pvc

#此时查看pv的状态 可以看到也已经绑定了
kubectl get pv

可以看到其中的STATUS字段现在为Bound表示已经处于绑定状态了,而且可以看到绑定的是pv0001。这说明我们前面定义的pv和这个pvc是匹配的,此时绑定会自动完成。

接下来要做的事情是将pvc与pod绑定。配置方法如下:

在 pod 的挂载容器配置中,增加 pvc 挂载

containers:

......

volumeMounts:

  • mountPath: /tmp/pvc

name: nfs-pvc-test

volumes:

  • name: nfs-pvc-test

persistentVolumeClaim:

claimName: nfs-pvc # pvc 的名称

创建名为 pvc-test-po.yaml 内容如下的配置文件。

apiVersion: v1
kind: Pod
metadata:
  name: test-pvc-po
spec:
  containers:
  - image: nginx
    name: nginx-volume
    volumeMounts:
    - mountPath: /usr/share/nginx/html # 挂载到容器的哪个目录
      name: test-volume # 挂载哪个 volume
  volumes:
  - name: test-volume
    persistentVolumeClaim: 
      claimName: nfs-pvc # 节点中的目录

#创建pod
kubectl create -f pvc-test-po.yaml

#查看pod
kubectl get po -o wide

#在node1机器的 /data/nfs/rw/test-pv 目录下创建index.hmtl文件
echo "hello word!" >> index.html


#curl test-pvc-po的ip(可以看到内容就是node1写入index.html的东西)
curl 10.244.94.111

至此,就通过PV/PVC实现了关联共享。

7.4、StorageClass(存储类)

每次都创建PV带来的一个明显问题就是应用越多PV的创建就越复杂。

为此,k8s 中提供了一套自动创建 PV 的机制,就是基于 StorageClass 进行的,通过 StorageClass 可以实现仅仅配置 PVC,然后交由 StorageClass 根据 PVC 的需求动态创建 PV(动态创建PV)。相当于PVC直接关联StorageClass,然后由StorageClass动态创建PV。

关于其使用此处就不演示了,后面用的时候再研究。

7.4.1、制备器(Provisioner)

每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。

7.4.2、NFS动态制备案例

(1)nfs-provisioner

创建名为 nfs-provisoner-deployment.yaml 内容如下的配置文件。

注: 这个就是制备器,负责完成具体的PV的构建。

注:这些内容看着很多但实际都是官方提供的,具体使用时一般只需要修改诸如volume很少一部分即可。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
  labels:
    app: nfs-client-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate 
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner  #需要调用k8s的很多接口去构建pv,这些是需要权限的
      containers:
        - name: nfs-client-provisioner
          #image: quay.io/external_storage/nfs-client-provisioner:latest
          image: registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME #
              value: fuseim.pri/ifs #对应到storageclass中的name
            - name: NFS_SERVER
              value: 10.206.0.10
            - name: NFS_PATH
              value: /data/nfs/rw
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.206.0.10
            path: /data/nfs/rw

(2)StorageClass配置

创建名为 nsf-storage-class.yaml 内容如下的配置文件。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
  namespace: kube-system
provisioner: fuseim.pri/ifs # 外部制备器提供者,编写为提供者的名称
parameters:
  archiveOnDelete: "false" # 是否存档,false 表示不存档,会删除 oldPath 下面的数据,true 表示存档,会重命名路径
reclaimPolicy: Retain # 回收策略,默认为 Delete 可以配置为 Retain
volumeBindingMode: Immediate # 默认为 Immediate,表示创建 PVC 立即进行绑定,只有 azuredisk 和 AWSelasticblockstore 支持其他值

(3)RBAC配置

创建名为 nsf-provisioner-rbac.yaml 内容如下的配置文件。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
  namespace: kube-system
rules:
  - 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
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    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
  namespace: kube-system
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
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

下面开始创建。

#先把rbac权限创建起来
kubectl apply -f nsf-provisioner-rbac.yaml

#把制备器的deployment创建好
kubectl apply -f nfs-provisoner-deployment.yaml

#创建storageclass
kubectl apply -f nsf-storage-class.yaml

#查看制备器
kubectl get po -n kube-system | grep nfs

#查看storageclass
kubectl get sc

(4)PVC处于Pending状态

在 k8s 1.20 之后,出于对性能和统一 apiserver 调用方式的初衷,移除了对 SelfLink 的支持,而默认上面指定的 provisioner 版本需要 SelfLink 功能,因此 PVC 无法进行自动制备。

可以通过配置不需要SelfLink的provisioner来解决这个问题。配置方法如下:

vim nfs-provisoner-deployment.yaml
修改其中的 spec.template.spec.containers.image 修改成如下阿里云镜像版本

registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0

#重新加载
kubectl apply -f nfs-provisoner-deployment.yaml

8、高级调度

除了cronjob和初始化容器,其他几个都比较强调调度的概念。

8.1、CronJob计划任务

在 k8s 中周期性运行计划任务,与 linux 中的 crontab 相同

注意:CronJob 执行的时间是 controller-manager 的时间,所以一定要确保 controller-manager 时间是准确的,另外 cronjob

┌───────────── 分钟 (0 - 59)

│ ┌───────────── 小时 (0 - 23)

│ │ ┌───────────── 月的某天 (1 - 31)

│ │ │ ┌───────────── 月份 (1 - 12)

│ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)

│ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat

│ │ │ │ │

│ │ │ │ │

* * * * *

创建名为 jobs/cron-job-test.yaml 内容如下的配置文件。

apiVersion: batch/v1
kind: CronJob
metadata:
  name: cron-job-test
spec:
  concurrencyPolicy: Allow # 并发调度策略:Allow 允许并发调度,Forbid:不允许并发执行,Replace:如果之前的任务还没执行完,就直接执行新的,放弃上一个任务
  failedJobsHistoryLimit: 1 # 保留多少个失败的任务
  successfulJobsHistoryLimit: 3 # 保留多少个成功的任务
  suspend: false # 是否挂起任务,若为 true 则该任务不会执行
#  startingDeadlineSeconds: 30 # 间隔多长时间检测失败的任务并重新执行,时间不能小于 10
  schedule: "* * * * *" # 调度策略
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox:1.28
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

#创建
kubectl create -f cron-job-test.yaml

#查看cronjob
kubectl get cj
kubectl describe cj cron-job-test

#查看执行日志(先找到pod,然后用 kubectl logs)
kubectl get po
kubectl logs cron-job-test-28525749-ggzqh

如下图所示,符合预期:

8.2、初始化容器

在真正的容器启动之前,先启动 InitContainer,在初始化容器中完成真实容器所需的初始化操作,完成后再启动真实的容器。

相对于 postStart 来说,首先 InitController 能够保证一定在 EntryPoint 之前执行,而 postStart 不能,其次 postStart 更适合去执行一些命令操作,而 InitController 实际就是一个容器,可以在其他基础容器环境下执行更复杂的初始化功能。

编辑前面的 nginx-deploy,添加如下内容至 spec.template.spec 下。

kubectl edit deploy nginx-deploy

      initContainers:  #想创建多个初始化容器就复制多个
      - image: nginx
        imagePullPolicy: IfNotPresent
        command: ["sh", "-c", "sleep 10;echo 'inited;' >> /.init"]
        name: init-test

kubectl get po


#进入容器看看
kubectl exec -it nginx-deploy-65585f54fd-8dfjj -- sh

可以看到容器会先进行Init,之后才会进入Runing的状态。

注:这里只是演示执行命令。但实际上因为初始化容器就是一个容器所以可以用来做很多更复杂的事情,例如构建一个镜像这个镜像专门用来做复杂的初始化动作。

8.3、污点(Taint)和容忍(Toleration)

k8s 集群中可能管理着非常庞大的服务器,这些服务器可能是各种各样不同类型的,比如机房、地理位置、配置等,有些是计算型节点,有些是存储型节点,此时我们希望能更好的将 pod 调度到与之需求更匹配的节点上。此时就需要用到污点(Taint)和容忍(Toleration),这些配置都是 key: value 类型的。污点和容忍度(Toleration)相互配合,就可以避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod, 是不会被该节点接受的。

中文官网:污点和容忍度 | Kubernetes

8.3.1、误点(Taint) ------ 打在节点上

污点:是标注在节点上的,当我们在一个节点上打上污点以后,k8s 会认为尽量不要将 pod 调度到该节点上,除非该 pod 上面表示可以容忍该污点,且一个节点可以打多个污点,此时则需要 pod 容忍所有污点才会被调度该节点。例如主节点对于一个集群非常重要,主节点的资源占用要十分慎重不能什么都往master上部,这个时候就可以给主节点打上污点。

#给节点 vm-32-7-opencloudos 打上一个"memory=low"的污点 污点影响设置为NoSchedule
kubectl taint no vm-32-7-opencloudos memory=low:NoSchedule

#查看节点的污点信息
kubectl describe no vm-32-7-opencloudos

#因为是 NoSchedule 配置,所以默认情况下在此节点上的pod并不受影响
kubectl get po -o wide

#移除污点
kubectl taint no vm-32-7-opencloudos memory=low:NoSchedule-


#查看master节点的默认误点信息
kubectl describe no k8s-master

#移除master节点的这个默认污点
kubectl taint no k8s-master node-role.kubernetes.io/master:NoSchedule-

#我们删除deploy部署的两个nginx-pod 待其重启后可以看到新节点就有可能部署到master节点上了
kubectl delete po nginx-deploy-65585f54fd-8dfjj nginx-deploy-65585f54fd-w2xmx

#pod可能会部署到master节点了
kubectl get po -o wide

如下图所示果然部署到master节点了。

#重新给master添加之前删除的污点,将影响改成 NoExecute;(预期master节点上的pod会被驱逐)
kubectl taint no k8s-master node-role.kubernetes.io/master:NoExecute

#观察确实如此
kubectl get po -o wide

如下图所示果然被驱逐了。

#对master节点在调整成初始状态
kubectl taint no k8s-master node-role.kubernetes.io/master:NoExecute-
kubectl taint no k8s-master node-role.kubernetes.io/master:NoSchedule

基于误点(Taint)实现了自动的调度管理!!!!

污点的影响:

  • NoSchedule:不能容忍的 pod 不能被调度到该节点,但是已经存在的节点不会被驱逐;
  • NoExecute:不能容忍的节点会被立即清除(强调驱逐),能容忍且没有配置 tolerationSeconds 属性,则可以一直运行,设置了 tolerationSeconds: 3600 属性,则该 pod 还能继续在该节点运行 3600 秒。

另外,系统还预留了一些有控制器自动添加的内置污点。

当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:

在节点被驱逐时,节点控制器或者 kubelet 会添加带有 NoExecute 效果的相关污点。 如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。

8.3.2、容忍(Toleration) ------ 打在任务(Pod)上

容忍:是标注在 pod 上的,当 pod 被调度时,如果没有配置容忍,则该 pod 不会被调度到有污点的节点上,只有该 pod 上标注了满足某个节点的所有污点,则会被调度到这些节点

  • Equal:比较操作类型为 Equal,则意味着必须与污点值做匹配,key/value都必须相同,才表示能够容忍该污点。
  • Exists:容忍与污点的比较只比较 key,不比较 value,不关心 value 是什么东西,只要 key 存在,就表示可以容忍。

Pod配置容忍示例如下。 参见 污点和容忍度 | Kubernetes

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"

#给nginx-deploy配置容忍
kubectl edit deploy nginx-deploy

#在 spec.template.spec(和containers平齐)下添加如下内容:

      tolerations:
      - key: "memory"
        operator: "Equal"
        value: "low"
        effect: "NoSchedule"

#然后看到可以运行到node2了
kubectl get po -o wide

8.4、亲和力(Affinity)

污点的作用是排斥任务,亲和力的作用恰恰相反。污点配合亲和力就能够实现更复杂、更智能的调度。亲和力(Affinity)相对于 nodeSelector 来说限定的没那么死,选择性会更多、拓展性更高。

参见官网: https://kubernetes.p2hp.com/docs/concepts/scheduling-eviction/assign-pod-node.html#affinity-and-anti-affinity

8.3.1、节点亲和力(NodeAffinity)

节点亲和力:进行 pod 调度时,优先调度到符合条件的亲和力节点上

节点亲和性概念上类似于 nodeSelector, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种:

  1. requiredDuringSchedulingIgnoredDuringExecution: 调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector, 但其语法表达能力更强(支持诸如In/NotIn/Exists/DoesNotExist/Gt/Lt等语义)。
  2. preferredDuringSchedulingIgnoredDuringExecution: 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。

接下来演示节点亲和力的效果。注:基于 nginx-deploy 进行编辑。

(1)首先查看pod所在节点的初始状态(可以看到都运行在node2上)

kubectl get po -o wide

(2)编辑nginx-deploy()。将官网上的关于亲和性的配置粘贴进去(spec.templata.spec下)。

      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution: #配置了一个kubernetes.io/os必维为linux的硬性条件
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/os
                operator: In
                values:
                - linux
          preferredDuringSchedulingIgnoredDuringExecution: #有配置了软性条件
          - weight: 1     #其中当存在label-1这个key且值为 key-1 时获得权重1
            preference:
              matchExpressions:
              - key: label-1
                operator: In
                values:
                - key-1
          - weight: 50    #其中当存在label-2这个key且值为 key-2 时获得权重50
            preference:
              matchExpressions:
              - key: label-2
                operator: In
                values:
                - key-2

(3)接下来我们给 node1 和 node2分别打标签。

#首先两个节点都打上硬性条件的标签
kubectl label no k8s-node1 kubernetes.io/os=linux
kubectl label no vm-32-7-opencloudos kubernetes.io/os=linux

#然后给node2打上 label-1=key-1 的标签
kubectl label no vm-32-7-opencloudos label-1=key-1

#给node1打上 label-2=key-2 的标签
kubectl label no k8s-node1 label-2=key-2

(4)可以看到此时两个pod就都被调度到node1节点了

在上述配置中我们使用到了匹配类型In,其实除了In之外还有很多其他匹配类型。

In:满足一个就行

NotIn:一个都不能满足

Exists:只要存在就满足

DoesNotExist:只有不存在才满足

Gt:大于节点上的数值才满足

Lt:小于节点上的数值才满足

8.3.2、Pod亲和力(PodAffinity)与Pod反亲和力(PodAntiAffinity)

**Pod 间亲和性与反亲和性使你可以基于已经在节点上运行的 Pod 的标签来约束 Pod 可以调度到的节点,而不是基于节点上的标签。**Pod 间亲和性与反亲和性的规则格式为"如果 node1 上已经运行了一个或多个满足规则 R1 的 Pod, 则当前 Pod 应该(或者在反亲和性的情况下不应该)运行在 node1 上"。 这里的 node1 可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域, Y 则是 Kubernetes 尝试满足的规则。

Pod 亲和力:将与指定 pod 亲和力相匹配的 pod 部署在同一节点。

Pod 反亲和力:根据策略将Pod1与Pod2尽量不部署到一个节点。

与节点亲和性类似,Pod 的亲和性与反亲和性也有两种类型:

  1. requiredDuringSchedulingIgnoredDuringExecution:硬性条件
  2. preferredDuringSchedulingIgnoredDuringExecution:软性条件

要使用 Pod 间亲和性,可以使用 Pod 规约中的 .affinity.podAffinity 字段。 对于 Pod 间反亲和性,可以使用 Pod 规约中的 .affinity.podAntiAffinity 字段。

具体使用参见官网。

7、身份与权限认证

7.1、认证

User Accounts: 不太需要关注。

Service Accounts:表示服务的身份即是谁;更多的是标识这个服务具备什么权限,即和权限关联。

#查看service accounts
kubectl get sa
kubectl get serviceaccount

7.2、授权(RBAC)

RBAC:基于角色的访问控制

(1)Role:代表一个角色,会包含一组权限,没有拒绝规则,只是附加允许。它是 Namespace 级别的资源,只能作用与 Namespace 之内。

(2)ClusterRole:功能与 Role 一样,区别是资源类型为集群类型,而 Role 只在 Namespace

#查看某个集群角色的信息
kubectl get clusterrole view -oyaml

(3)RoleBinding:Role 或 ClusterRole 只是用于制定权限集合,具体作用与什么对象上,需要使用 RoleBinding 来进行绑定。

(4)ClusterRoleBinding:与 RoleBinding 相同,但是作用于集群之上,可以绑定到该集群下的任意 User、Group 或 Service Accoun

辅助参考:kubeadm安装k8s集群-CSDN博客

相关推荐
福大大架构师每日一题11 小时前
22.1 k8s不同role级别的服务发现
容器·kubernetes·服务发现
weixin_4539650012 小时前
[单master节点k8s部署]30.ceph分布式存储(一)
分布式·ceph·kubernetes
weixin_4539650012 小时前
[单master节点k8s部署]32.ceph分布式存储(三)
分布式·ceph·kubernetes
tangdou36909865512 小时前
1分钟搞懂K8S中的NodeSelector
云原生·容器·kubernetes
Lansonli13 小时前
云原生(四十一) | 阿里云ECS服务器介绍
服务器·阿里云·云原生
Dylanioucn14 小时前
【分布式微服务云原生】掌握分布式缓存:Redis与Memcached的深入解析与实战指南
分布式·缓存·云原生
later_rql15 小时前
k8s-集群部署1
云原生·容器·kubernetes
weixin_4539650017 小时前
[单master节点k8s部署]31.ceph分布式存储(二)
分布式·ceph·kubernetes
大G哥20 小时前
记一次K8S 环境应用nginx stable-alpine 解析内部域名失败排查思路
运维·nginx·云原生·容器·kubernetes
feng_xiaoshi20 小时前
【云原生】云原生架构的反模式
云原生·架构