知足常乐

知足常乐

再撘一套Kubernetes集群

2021-06-18

本文地址:https://blog.focc.cc/archives/132

嫖的服务器到期了~没有环境可玩了,那就在本地用虚拟机再撘一套!顺便再温习一遍相关知识,
Kubeadm 是一个提供了 kubeadm init 和 kubeadm join 的工具, 作为创建 Kubernetes 集群的 “快捷途径” 的最佳实践。

阅读本文前默认您已经了解k8s相关知识,适用于想快速部署进行开发

1.环境准备

1.1安装虚拟机

准备三台以上Linux服务器(虚拟机)
我这里使用centos7.6作为镜像文件创建三台虚拟机

镜像文件以百度网盘方式分享(阿里网盘我还没分享功能资格)
链接:https://pan.baidu.com/s/12vbdwTiTB6hfxroxSovEJw
提取码:m5by

配置要求:2G以上\30G硬盘\2颗cpu核心

image.png

1.2系统初始化

以下操作没有特殊说明默认在每台服务器上都执行命令

关闭防火墙

systemctl stop firewalld
systemctl disable firewalld

关闭 selinux

sed -i 's/enforcing/disabled/' /etc/selinux/config
setenforce 0

关闭 swap

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

根据规划设置主机名
这里针对不同虚拟机设置不同名称

hostnamectl set-hostname <hostname>

master
image.png
node1
image.png
node2
image.png

在 master节点 添加 其它两个节点hosts

cat >> /etc/hosts << EOF
192.168.182.128 k8smaster
192.168.182.129 k8snode1
192.168.182.130 k8snode2
EOF

设置网络

cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF

让配置生效

sysctl --system

同步服务器时间

yum install ntpdate -y
ntpdate time.windows.com

1.3安装依赖环境

每台服务器安装Docker

wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce-18.06.1.ce-3.el7
systemctl enable docker && systemctl start docker

修改docker源

cat > /etc/docker/daemon.json << EOF
{
  "registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF

修改k8s的阿里yum源

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

安装kubeadm,kubelet和kubectl

yum install -y kubelet-1.18.0 kubeadm-1.18.0 kubectl-1.18.0
systemctl enable kubelet

2.部署k8s节点

在master节点启动相关组件
注意把对应ip改成你的master节点的ip(192.168.182.128)

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

执行完毕查看结果 会看到successfully!下边有一段脚本

Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.182.128:6443 --token 5qvw4s.b7fd0vl7gg9gc600
--discovery-token-ca-cert-hash sha256:b18f63757d14f9d26b853b8e94bda10d4751c988c58c4f9da5a4e7dd7df1d141

以下操作基于上边安装成功的信息来操作(执行的时候为你安装成功的脚本信息)

我们复制上边的脚本在master节点执行

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

然后看上边最后一段脚本 在node节点 使用kubeadm join命令把node加入到master节点

kubeadm join 192.168.182.128:6443 --token 5qvw4s.b7fd0vl7gg9gc600 \
    --discovery-token-ca-cert-hash sha256:b18f63757d14f9d26b853b8e94bda10d4751c988c58c4f9da5a4e7dd7df1d141 

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

kubeadm token create --print-join-command

部署CNI网络插件

下载这个yml文件之后修改下名字

wget https://blog.focc.cc/upload/2021/12/kube-flannel-119ad0843ae849fa8a5cdeae3b6dc2b0.yaml
mv kube-flannel-119ad0843ae849fa8a5cdeae3b6dc2b0.yaml kube-flannel.yaml
kubectl apply -f kube-flannel.yaml

查看集群状态
使用kubectl命令查看节点状态

kubectl get nodes
NAMESTATUSROLESAGEVERSION
k8smasterReadymaster64mv1.18.0
k8snode1Ready57mv1.18.0
k8snode2Ready56mv1.18.0

3.集群测试

使用deployment创建一个nginx应用

kubectl create deployment nginx --image=nginx

查看应用的状态

kubectl get po

image.png

使用expose对外暴露访问端口

kubectl expose deployment nginx --port=80 --type=NodePort

查看暴露的端口号

kubectl get pod,svc

image.png

在浏览器中随便找一个集群的NodeIP进行访问
192.168.182.128:31741
192.168.182.129:31741
192.168.182.130:31741

image.png

image.png

4.Yml文件

上边我们已经把集群搭建起来并使用命令创建了一个可以对外提供的服务,但是如果我们要创建一些比较复杂的大型应用,这种创建方式是不可取的.总不能每次创建应用都要手敲一大串命令来执行把~~

我们在启动java程序的时候或者跑一个批处理任务的时候,你是不是要写很多变量参数供程序来读取并执行,k8s中的大部分资源都可以通过yml文件来配置.

4.1简单的yml文件什么样?

人类的创造力是最神奇的,但是人类的模仿能力也是很强大的!
我们可以先看别人写的yml文件是什么样子,然后再模仿去写,久而久之...

我先列一下yml中一些必要的字段解释

参数名字段类型说明
versionStringK8s API的版本,使用kubectl api-versions 可以查询到,目前基本都是基于v1
kindString指当前的yml定义的资源类型和角色,例如:pod、service
metadataObject元数据对象,固定值写metadata
metadata.nameString元数据对象的名字,例如Pod的名字
metadata.namespaceString元数据对象的命名空间,用来隔离资源,例如default、自定义xxx
SpecObject详细定义对象,固定值写Spec
Spec.container[]list这里是spec对象的容器列表定义
Spec.container[].nameString定义容器的名字
Spec.container[].imageString镜像的名称

上边这些只是最基本的参数,k8s中的参数有超级多!用到的时候可以去搜索引擎上边搜索对应的含义

4.2如何使用yml文件?

我们在搭建集群的时候使用 kubectl create 创建了一个nginx的服务,
这个命令其实就是帮我们写了一个yml,并且执行了.

我们使用命令再创建一个nginx的服务但是不让运行,导出它的yml看一下

kubectl create deployment app-nginx --image=nginx -o yaml --dry-run > nginx.yaml

可以根据我们上边列出的yml字段和下边的对照一下

[root@k8smaster ~]# cat nginx.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: app-nginx
  name: app-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: app-nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

然后我们就可以基于这个yml文件修改一些东西并使用apply命令来创建这个资源

kubectl apply -f nginx.yaml

可以看到我们又创建一个app-nginx的服务
image.png

5.Pod详解

Pod 是 k8s 系统中可以创建和管理的最小单元, 是资源对象模型中由用户创建或部署的最
小资源对象模型, 也是在 k8s 上运行容器化应用的资源对象, 其他的资源对象都是用来支
撑或者扩展 Pod 对象功能的, 比如控制器对象是用来管控 Pod 对象的, Service 或者
Ingress 资源对象是用来暴露 Pod 引用对象的, PersistentVolume 资源对象是用来为 Pod
提供存储等等, k8s 不会直接处理容器, 而是 Pod, Pod 是由一个或多个 container 组成
Pod 是 Kubernetes 的最重要概念, 每一个 Pod 都有一个特殊的被称为” 根容器“的 Pause
容器。 Pause 容器对应的镜 像属于 Kubernetes 平台的一部分, 除了 Pause 容器, 每个 Pod
还包含一个或多个紧密相关的用户业务容器

太长不看,我们都知道容器可以通过docker来创建,创建出来的容器是单进程的,pod就是一组容器的集合,是多进程了.通俗理解就是k8s不管理单独的容器,它管理的是一组容器,也就是一个pod,一般情况下,我们一个pod就定义一个容器.所以也可以理解成k8s管理pod就是管理一个容器一个服务.我们看张图就直观了

image.png

5.1 Pod是什么?

我们假设现在有个应用,里边用了mysql、redis(非传统部署)
那我们用k8s怎么来启动这样的应用呢,
我们定义pod资源,里边有三个容器,第一个是我们应用自身的程序容器,第二个是mysql的容器,第三个是redis的容器.这样一组容器的集合就是一个POD,我们在启动应用的时候,就会启动是三个服务三个进程,我们只需要针对Pod来进行管理就行了~一般我们一个pod里边只定义一个容器即可.

5.2 Pod的生命周期

我们在前边创建nginx的时候也看到了pod的状态为Running,其实它有很多状态

状态说明
PendingAPI Server已经创建了该Pod,但Pod中的一个或多个容器的镜像还没有穿件,准备过程
RunningPod内所有的容器已创建,且至少一个容器处于运行状态、正在启动状态。正在重启状态
CompletedPod内所有的容器均成功执行退出,且不会再重启
FailedPod内所有容器均已退出,但至少一个容器退出失败
Unknown由于某种原因无法获取Pod状态,例如网络通信不佳

5.3 Pod的重启策略

以上边的nginx配置为例 我们设置restartPolicy的策略

    spec:
      containers:
      - image: nginx
        name: nginx
      restartPolicy: Never
重启策略说明
Always当容器失效时,由kubelet自动重启该容器
OnFailure当容器终止运行且退出吗不为0时,由kubelet自动重启该容器
Never不论容器运行状态如何,kubelet都不会重启该容器

5.4 Pod的镜像拉取策略

还是来看我们之前创建nginx的deployment时候的yml文件
在containers这个属性下边 我们可以设置imagesPullPolicay的策略

    spec:
      containers:
      - image: nginx
        name: nginx
	imagePullPolicy: Always
        resources: {}
镜像拉取策略说明
IfNotPresent默认值, 镜像在宿主机上不粗糙你在的时候才会拉取
Always每次创建Pod都会重新拉取一次镜像
NeverPod 永远不会主动拉取这个镜像

5.4针对Pod的资源配置

每个 Pod 都可以对其能使用的服务器上的计算资源设置限额, Kubernetes 中可以设置限额
的计算资源有 CPU 与 Memory 两种, 其中 CPU 的资源单位为 CPU 数量,是一个绝对值而非相
对值。 Memory 配额也是一个绝对值, 它的单 位是内存字节数。
Kubernetes 里, 一个计算资源进行配额限定需要设定以下两个参数: Requests 该资源最
小申请数量, 系统必须满足要求 Limits 该资源最大允许使用的量, 不能突破, 当容器试
图使用超过这个量的资源时, 可能会被 Kubernetes Kill 并重启

说人话就是对pod进行一些资源的设置(cpu 内存),在k8s调度节点的时候按照条件来寻找
还是以刚刚的nginx配置文件来实例

    spec:
      containers:
      - image: nginx
        name: nginx
        resources: 
	  requests:
	    memory: "64Mi"
	    cpu: "250m"
	  limits:
	    memory: "128Mi"
	    cpu: "500m"

按照上边在resources属性中设置 requests和limits的值
这里的cpu单位是绝对值, 250m代码使用0.25核
这里的配置指明,k8s在调度的时候,这个pod最低要使用0.25核和64M内存的资源, 下边的是最大限制

5.5 Pod的健康检查

我们在前边知道了pod的几种状态.
我们详细看下Running状态的说明
|Pod内所有的容器已创建,且至少一个容器处于运行状态、正在启动状态。正在重启状态|
假设我们的容器已经创建,并且正在启动,但是数据库连接失败了.这个时候我们看pod状态是正常的
但是以应用层面来说,这个应用是无法对外提供服务的.所以我们得有一种策略从应用层面检查应用是否正常
一般生产环境我们可以通过检查httpGet方式,向服务发送http请求,看响应码是否正常来决定服务是否正常

我们还是以nginx的yml来配置说明

    spec:
      containers:
      - image: nginx
        name: nginx
      livenessProbe:
	httpGet:
	  path: /index
	initialDelaySeconds: 5
	periodSecondes: 5
	failureThreshold: 3
策略说明
livenessProbe如果检查失败,就杀死容器,根据pod的restartPolicay来操作
readinessProbe如果检查失败,K8s会把pod从service endpoints中剔除

Probe支持三种检查方法

方法说明
httpGet发送HTTP请求,返回200-400范围状态码为成功
exec执行shell命令返回状态码是0为成功
tcpSocket发起TCP Socket建立成功

每种方式通用的参数含义

参数说明
initialDelaySeconds容器启动后开始探测之前需要等待多少秒,如果应用启动一般30s的话,就设置为30s
periodSeconds执行探测的频率(多少秒执行一次),默认为10s,最小值为1
successThreshold探针失败后,最少连续多少次才视为成功,默认值为1。最小值为1
failureThreshold最少连续多少次失败才视为失败。默认值为3,最小值为1

5.6 Pod的调度策略

我们在前边理解了Pod的资源配置,我们可以配置Pod的最小调度cpu和内存,
其实这个就算是影响Pod的调度策略了.它只会去寻找满足条件的Node来进行部署。

5.6.1 节点选择器

我们以nginx配置为例
可以看到下边我们配置了两个nginx的yml
我们用nodeSelector标签配置了(env:xxx)这个值, 代表将我们的pod调度到具有env标签的node机器
第一个调度到具有env标签并且值为dev的Node节点上
第二个调度到具有env标签并且值为prod的Node节点上

    spec:
      nodeSelector:
	env:dev
      containers:
      - image: nginx
        name: nginx
    spec:
      nodeSelector:
	env:prod
      containers:
      - image: nginx
        name: nginx

那Node的节点又是如何设置的呢?
我们可以使用label命令来为node打标签如下

kubectl label node k8snode1 env=dev
kubectl label node k8snode2 env=prod

kubectl get nodes k8snode1  --show-labels
kubectl get nodes k8snode2  --show-labels

这样我们就分别为两台Node节点打上了不同的标签,通过命令查看node节点具有的标签

5.6.2 节点亲和性

节点亲和性也和node的标签关联,但是和上边的节点选择器不太一样 依然是用nginx的配置文件来说明
这个nodeAffinity标签的属性着实有点多,但是我们只需要关注里边的matchExpressions标签的值即可,
先说一下两种亲和性的特点

亲和性说明
requiredDuringSchedulingIgnoredDuringExcution硬亲和性,如果没有满足matchExpressions条件,那就一直调度寻找
preferredDuringSchedulingIgnoreDuringExcution软亲和性,如果没有满足matchExpressions条件,那别的节点也可以

结合下边的配置文件来看

    spec:
      arrinity:
	nodeAffinity:
	  requiredDuringSchedulingIgnoredDuringExcution:
	    nodeSelectorTerms:
	    - matchExpressions:
	      - key: env
	        operator: In
		values:
		- dev
		- dev1
      containers:
      - image: nginx
        name: nginx

寻找带有标签env并且值为dev或者dev1的来调度, 没有的话Pod的状态就一直为Pending

    spec:
      arrinity:
	nodeAffinity:
	  preferredDuringSchedulingIgnoreDuringExcution:
	  - weight:1
	    preferencd:
	      matchExpressions:
	      - key: group
		operator: In
		values:
		- public
      containers:
      - image: nginx
        name: nginx

寻找带有标签group并且值为public的来调度, 找一遍没有话,再根据其它调度规则来调度。

常用的operator操作符有 In、NotIn、Exists、Gt、Lt、DonseNotExists

5.6.3 污点策略

上边我们说的几种方法都是基于Pod来进行配置的,还有一种就是设置Node的污点属性主动来管理Pod的调度规则

我们先看一下master节点和非master节点的污点属性

[root@k8smaster ~]# kubectl describe node k8smaster | grep Taint
Taints:             node-role.kubernetes.io/master:NoSchedule
[root@k8smaster ~]# kubectl describe node k8snode1 | grep Taint
Taints:             <none>
[root@k8smaster ~]# kubectl describe node k8snode2 | grep Taint
Taints:             <none>

可以看到master节点的Taint属性有个 node-role.kubernetes.io/master:NoSchedule 值
先说以下污点的三个类型

污点类型说明
NoSchedule一定不会调度
PreferNoSchdule尽量不被调度
NoExecute不会被调度,并且驱逐Node已有的Pod

所以master节点有NoSchedule属性 pod一般不会调度到master节点,为什么说一般不会呢,后边再说

如何给Node打污点

使用taint命令给node1节点打上NoSchedule类型的污点

kubectl taint node k8snode1 group=public:NoSchedule

污点容忍
上边我们NoSchedule类型污点一定不会被调度,那我为什么说master节点一般不会被调度呢
我们还是以nginx配置文件来说明 以刚刚node1节点为例

    spec:
      tolerations:
      - key: "group"
        operator: "Equal"
	value: "public"
	effect: "NoSchedule"
      containers:
      - image: nginx
        name: nginx

上边的tolerations代表 我们可以调度到污点类型为NoSchedule key为group value为public的,操作符同上边的也有很多种

关于Pod基本策略我们就先了解到这里, 我们在创建nginx的时候用到了kubectl create deployment 命令,那这个deployment是什么呢?我们下边来看看

6.Controller

我们知道k8s号称容器编排工具,具备容灾、动态扩缩容等等实现自动化运维的功能,那么我们在前边已经认识到了Pod的概念,Controller就是更高层面的管理运行容器的对象. controller有很多种,我们一般现在用的就是deployment来部署应用

6.1 使用deployment部署应用

我们还是以nginx来为例,使用deployment进行部署应用

创建yml模板文件

kubectl create deployment nginx --image=nginx --dry-run -o yaml > nginx.yaml

我们来对这个yml分析一下
kind代表资源类型:Deployment
metadata中的name属性代表资源的名称:nginx

spec中就是一些调度的属性了
replicas是指运行几个副本,简单理解一个副本就对应一个Pod,我们在这里修改为3个
deployment与pod之间是通过标签来关联的。
也就是selector.matchLabels.app 这个属性 与containers[n].name 这个属性关联, 这里都为nginx

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
status: {}

修改之后我们使用命令创建这个副本

kubectl apply -f nginx.yaml

查看状态

[root@k8smaster k8s]# kubectl get deploy
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   3/3     3            3           99s
[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS    RESTARTS   AGE
nginx-f89759699-gz8dd   1/1     Running   0          101s
nginx-f89759699-l9n6z   1/1     Running   0          101s
nginx-f89759699-zt547   1/1     Running   0          101s

我们创建完副本之后,这些pod只能内部访问
我们要通过service来对外暴露端口
我们创建一个service的yml文件看一下

kubectl expose deployment nginx --port=80 --type=NodePort --target-port=80 --name=nginx --dry-run -o yaml > nginx-svc.yaml

其实yml里边的内容就是我们刚刚输入的命令参数
kind代表资源类型,是Service
metadata定义Service的名字
spec定义一些规则
port代表内部通信的端口
protocol代表传输协议
targetPort代表容器内的端口,nginx暴漏的是80端口
selector.app 与pod标签一致
type代表以NodePort方式暴漏,有很多种
具体的属性可以参考文末的参考链接

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
status:
  loadBalancer: {}

我们使用命令创建这个Service

kubectl apply -f nginx-svc.yaml

查看状态

[root@k8smaster k8s]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        4d16h
nginx        NodePort    10.104.57.126   <none>        80:30371/TCP   11s

通过集群的任意Node节点IP+30371端口来访问服务
192.168.182.128:30371
192.168.182.129:30371
192.168.182.130:30371

image.png

6.2 版本升级

我们再来看下刚刚创建的deployment的yml文件
在spec.containers[n].image属性是填写镜像名称的,这里我们没有指定版本就是默认拉取latest最新版本的
我们现在把刚刚创建的depolyment删掉,并指定nginx的版本来创建一下

    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}

删除deploy和svc

kubectl delete deploy nginx
kubectl delete svc nginx

修改nginx版本指定为1.20

    spec:
      containers:
      - image: nginx:1.20
        name: nginx
        resources: {}

创建nginx

kubectl apply -f nginx.yaml

创建svc

kubectl apply -f nginx-svc.yaml

我们可以通过nodePort端口来访问nginx查看版本为1.20
image.png

我们现在使用命令升级nginx的版本到1.21

kubectl set image deployment nginx nginx=nginx:1.21

查看升级结果为 deployment "nginx" successfully rolled out

kubectl rollout status deployment nginx

我们看一下nginx的版本

image.png

如果你在执行完升级命令之后去查看pod的状态你会发现你设置的副本数为3,但是当前的pod绝对大于3个,但是状态可能不同.
这个升级怎么理解呢,就像一个队列,先进先出,三个老版本的pod,当有一个新版本的pod起来之后,就会把老版本的剔除掉,知道所有pod都更新换代!这期间,服务不会停止。这就是不停机维护升级版本。

6.3 版本回退

生产情况下,我们把版本升级之后,有用户返回新版本好像有BUG!影响很大,这个时候怎么办。k8s也提供了版本回退机制,你可以一键回退到上个版本

我们先看一下版本的管理 有两个版本,第一个就是最开始的1.20,第二个就是我们升级的1.21

[root@k8smaster k8s]# kubectl rollout history deployment nginx
deployment.apps/nginx 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

回滚到上一个版本

kubectl rollout undo deployment nginx

这次我截了个图 可以明显看到,下边三个是新创建的pod, 已经有两个处于Running状态了,那就证明上边必定有两个已经停止了服务处于Terminating状态,一会就会消失了~ 和上边升级版本的过程是一样的,回退也是不停机的
image.png
查看回滚结果

[root@k8smaster k8s]# kubectl rollout status deployment nginx
deployment "nginx" successfully rolled out

我们既然有了版本管理的概念,那我们能否回滚到某个指定版本呢?是可以的

回滚到指定版本 通过--to-revision回滚到某个版本

kubectl rollout undo deployment nginx --to-revision=2

6.4 弹性伸缩

当生产环境的服务访问量高峰期时,我们可以动态的增加容器的数量来分担流量

kubectl scale deployment nginx --replicas=5

6.5 一些特殊的Controller

当我们创建副本数量大于1个时,这些pod启动是无序的,而且每个pod都是一样的,可以进行随意伸缩,我们如果想为每一个pod指定一个唯一标识符,该如何做?如果我们只需要这个pod运行一次就停止,或者定时运行一个pod该怎么做?

6.5.1 StatefulSet

我们上边创建服务均使用的Deployment,我们可以使用StatefulSet来创建Pod
我们看一下这个yml,中间以 "---"符号隔开代表把两个yml写在一起了,创建的时候使用这一个文件即可

首先,我们创建了一个Service,它的name是nginx-sfs,最重要的是它的ClusterIP我们设置了None
接下来创建了一个StatefulSet类型的Controller 这个和前边是一样的,只要注意Service和Controller的标签一致即可。

apiVersion: v1
kind: Service
metadata:
  name: nginx-sfs
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: nginx-sfs
  clusterIP: None
  selector:
    app: nginx

---

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-sfs
spec:
  serviceName: nginx-sfs
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80

使用命令创建它,并查看它们的状态
可以看到nginx-sfs的ClusterIP是None
pod的name是以nginx-sfs-序列号来创建的,并且会给每个pod分配一个固定的域名,这个域名解析对应的pod内部IP
StatefulSet中每个Pod的DNS格式为statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
这一块的东西可以看一下文末的参考链接中的官方文档。

StatefulSet的作用是很大的,例如可以应用到mysql主从,来确定标识进行读写分离。

[root@k8smaster k8s]# kubectl apply -f nginx-sfs.yaml 
service/nginx-sfs created
statefulset.apps/nginx-sfs created
[root@k8smaster k8s]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP        4d22h
nginx        NodePort    10.111.59.136   <none>        80:32526/TCP   11m
nginx-sfs    ClusterIP   None            <none>        80/TCP         30s
[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS    RESTARTS   AGE
nginx-f54648c68-56sqr   1/1     Running   0          44m
nginx-f54648c68-b7b7d   1/1     Running   0          44m
nginx-f54648c68-cv58s   1/1     Running   1          5h57m
nginx-f54648c68-rr2jr   1/1     Running   0          44m
nginx-f54648c68-xmsr2   1/1     Running   0          44m
nginx-sfs-0             1/1     Running   0          4m2s
nginx-sfs-1             1/1     Running   0          3m41s
nginx-sfs-2             1/1     Running   0          3m19s

6.5.2 DaemonSet

加入我们有个服务,是收集每台node节点的性能数据,我们要让每台node都部署一个这样的探针,并且后边新加进来的node节点也要自动部署这个探针服务,我们就可以使用DaemonSet

我们看一下这个yml, kind被标识为DaemonSet,这里边的volumes后边会说,我们创建这个,来收集nginx产生的日志,每台node节点都部署一个.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-ds
  labels:
    app: nginx-ds
spec:
  selector:
    matchLabels:
      app: nginx-ds
  template:
    metadata:
      labels:
        app: nginx-ds
    spec:
      containers:
      - name: logs
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: varlog
          mountPath: /tmp/log
      volumes:
      - name: varlog
        hostPath:
          path: /var/log

我们使用命令创建它并查看状态

[root@k8smaster k8s]# kubectl apply -f nginx-ds.yaml 
daemonset.apps/nginx-ds created
[root@k8smaster k8s]# kubectl get daemonSet
NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
nginx-ds   2         2         2       2            2           <none>          26s
[root@k8smaster k8s]# kubectl get po -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
nginx-ds-4w8wj          1/1     Running   0          80s     10.244.2.22   k8snode2   <none>           <none>
nginx-ds-t6q9w          1/1     Running   0          80s     10.244.1.7    k8snode1   <none>           <none>

可以看到每台node节点都部署了一个nginx-ds服务

6.5.3 Job(一次性任务)

我们要部署一个批处理任务,执行一次即可怎么做?
kind指定为Job类型, 容器使用perl,进行圆周率的输出

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

创建并查看结果

可以看到pod状态为Completed代表pod的程序已经运行完成,
使用get jobs命令查看job也运行完成

[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS      RESTARTS   AGE
pi-ltjzl                0/1     Completed   0          3m19s
[root@k8smaster k8s]# kubectl get jobs
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           80s        3m28s

我们使用命令查看pod的日志看下

[root@k8smaster k8s]# kubectl logs pi-ltjzl
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

6.5.4 CronJob(定时任务)

每隔一段时间我们就让它执行一次任务的服务怎么部署?
指定kind类型为CronJob
schedule参数为cron表达式 这里是每分钟
具体内容是输入 Hello from the Kubernetes cluster

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

创建并查看结果
我们可以看到已经运行2m31s,上次运行是34s前
看到有两个pod的状态为Completed

[root@k8smaster k8s]# kubectl get cronjob
NAME    SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   */1 * * * *   False     1        34s             2m31s
[root@k8smaster k8s]# kubectl get po
NAME                     READY   STATUS      RESTARTS   AGE
hello-1623684840-v79g9   0/1     Completed   0          110s
hello-1623684900-pw8g2   0/1     Completed   0          50s

查看pod内日志

[root@k8smaster k8s]# kubectl logs hello-1623684900-pw8g2
Mon Jun 14 15:35:46 UTC 2021
Hello from the Kubernetes cluster

要注意的是,定期任务,是每到执行时间便创建一个pod来执行任务,执行完pod状态就是Completed

至此,我们就可以动态的对集群内部的应用进行管理配置,还有几种适用于不同场景的Controller,
其中我们创建了Service来对外暴漏nodePort端口来提供服务,这个Service是什么呢?

7.Service

Service 是 Kubernetes 最核心概念, 通过创建 Service,可以为一组具有相同功能的容器应
用提供一个统一的入口地 址, 并且将请求负载分发到后端的各个容器应用上

7.1 Service是什么?

我们先用命令查看一下pod的详细描述
可以看到IP地址都是虚拟的,Pod这个概念时短暂的,只要你node重启或者副本伸缩,这个IP都会发生变化,
我们知道,服务之间通信最基本的都要基于IP+端口来传输数据,那么IP老是变化怎么办?那就是Service做的事情了.

[root@k8smaster ~]# kubectl get po -o wide
NAME                    READY   STATUS    RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
nginx-f54648c68-56sqr   1/1     Running   0          2m39s   10.244.2.19   k8snode2   <none>           <none>
nginx-f54648c68-b7b7d   1/1     Running   0          2m39s   10.244.1.4    k8snode1   <none>           <none>
nginx-f54648c68-cv58s   1/1     Running   1          5h15m   10.244.2.18   k8snode2   <none>           <none>
nginx-f54648c68-rr2jr   1/1     Running   0          2m39s   10.244.2.20   k8snode2   <none>           <none>
nginx-f54648c68-xmsr2   1/1     Running   0          2m39s   10.244.1.3    k8snode1   <none>           <none>

可以把Service看作是一个服务注册中心,创建的pod信息都被注册到上边,需要通信的时候,就去服务中心中去发现,找到对应的IP建立链接,而且它还可以指定负载策略,五个pod,到底哪个接受请求,就是它做的事情。

Service和pod如何建立关系呢?我们看一下创建Service时候的yml
可以看到labels.app:nginx 和 selector.app:nginx是一致的,
创建deployment时,我们给的标签也是app:nginx,它们是通过标签来进行关联的。

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
status:
  loadBalancer: {}

7.1 Service的三种类型

类型说明
ClusterIP默认类型,供集群内部使用
NodePort对外暴漏端口,供外部应用访问使用
LoadBalancer对外访问应用,适用于公有云

我们看上边Service的yml文件,其实可以看到我们指定了type:NodePort,这个时候创建svc之后,我们就可以使用任意node节点的IP+端口来访问我们的服务了~

那默认类型怎么理解呢,如果使用ClusterIP类型,那么就默认只能集群内部访问,使用任意node节点内部进行curl请求或者其它连接请求即可访问.
我们可以验证以下,我们把创建好的svc删除

[root@k8smaster ~]# kubectl delete svc nginx
service "nginx" deleted
[root@k8smaster k8s]# vi nginx-svc.yaml 
[root@k8smaster k8s]# kubectl apply -f nginx-svc.yaml 
service/nginx created
[root@k8smaster k8s]# kubectl get svc
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1     <none>        443/TCP   4d22h
nginx        ClusterIP   10.99.42.89   <none>        80/TCP    7s

可以看到我们查询svc之后,TYPE类型变为ClusterIP,这个时候我们在集群内部任一节点来访问这个IP
可以看到,访问到了nginx服务

[root@k8snode1 ~]# curl 10.99.42.89
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

8.Secret

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

8.1 Secret的三种类型

类型说明
Opaquebase64编码格式的Secret,常用于存储密码、密钥等
kubernetes.io/dockerconfigjson用来存储私有 docker registry的认证信息
ServiceAccount用来访问KubernetesAPI,由Kubernetes自动创建,并且会自动挂在到Pod的/run/secrets/kubernetes.io/serviceaccount目录中

8.2 如何使用Opaque类型的Secret

我们在创建容器的时候通常会给容器内传入参数变量,我们通过两种方式来说明如何挂载Secret到Pod中

8.2.1 以变量形式

Opaque都是以base64编码来传输的,
我们先编码两个字符串来模拟账号和密码
admin -> YWRtaW4=
fangpengbo -> ZmFuZ3Blbmdibw==

我们看下边的yml文件,kind类型为Secret,name为secret-Opaque,有两个参数一个是username,一个是password,对应的value值是我们上边进行base64的。

apiVersion: v1
kind: Secret
metadata:
  name: secret-opaque
type: Opaque
data:
  username: YWRtaW4=
  password: ZmFuZ3Blbmdibw==

创建并查看它

[root@k8smaster k8s]# kubectl apply -f secret.yaml 
secret/secret-opaque created
[root@k8smaster k8s]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
secret-opaque         Opaque                                2      15s

我们还是以创建nginx来挂载参数为例
在env属性下边我们挂在两个环境变量,关联到secret-opaque这个secret
分别取username和password为key

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: secret-opaque
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: secret-opaque
            key: password

创建之后,进入容器内查看环境变量

[root@k8smaster k8s]# kubectl apply -f secret-var.yaml 
pod/mypod created
[root@k8smaster k8s]# kubectl get po
NAME                    READY   STATUS    RESTARTS   AGE
mypod                   1/1     Running   0          44s
[root@k8smaster k8s]# kubectl exec -it mypod bash
root@mypod:/# echo $SECRET_USERNAME
admin
root@mypod:/# echo $SECRET_PASSWORD
fangpengbo
root@mypod:/# 

8.2.2 以配置文件形式挂载

我们把创建的secret以配置文件的形式直接挂载到pod内部
不一样的地址在于volumeMounts这个属性,
mountPath说明挂载到容器内部的哪个路径
volumes就是secret列表

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: secret-opaque

创建并进入容器内部查看它

[root@k8smaster k8s]# kubectl exec -it mypod bash
root@mypod:/# cd /etc/foo/
root@mypod:/etc/foo# ls
password  username
root@mypod:/etc/foo# cat password 
fangpengbo
root@mypod:/etc/foo# cat username 
admin

9.ConfigMap

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

configMap和我们上个Secret类似,不过是configMap更像一个配置文件,用来保存一些配置信息供我们的pod读取.我们来看下如何使用configMap来挂载到pod中

9.1 如何使用configMap

我们现在有一个redis的配置文件如下

redis.host=127.0.0.1
redis.port=6379
redis.password=123456

我们来创建并查看它

[root@k8smaster k8s]# kubectl create configmap redis-config --from-file=redis.properties
configmap/redis-config created
[root@k8smaster k8s]# kubectl get cm
NAME           DATA   AGE
redis-config   1      7s
[root@k8smaster k8s]# kubectl describe cm redis-config
Name:         redis-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis.properties:
----
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

Events:  <none>

下边我们还是创建一个nginx容器,用volume方式挂载到容器内部
我们可以看到在configMap属性中指定我们刚刚创建的redis-config
创建完pod之后我们输出以下这个配置文件

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh","-c","cat /etc/config/redis.properties" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: redis-config

创建pod并查看日志



下边我们以变量的形式挂载到pod中
我们创建了一个kind类型为ConfigMap的资源
data绑定了两个值

apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfig
  namespace: default
data:
  special.level: info
  special.type: hello

创建它

[root@k8smaster k8s]# kubectl apply -f myconfig.yaml 
configmap/myconfig created
[root@k8smaster k8s]# kubectl get cm
NAME           DATA   AGE
myconfig       2      8s

创建pod挂载
我们挂载到env中

apiVersion: v1
kind: Pod
metadata:
  name: mypod1
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh", "-c", "echo $(LEVEL) $(TYPE)" ]
      env:
        - name: LEVEL
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.level
        - name: TYPE
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.type
  restartPolicy: Never

创建并查看日志

[root@k8smaster k8s]# kubectl logs mypod1
info hello

10.Ingress-Nginx

在前边我们创建了一个pod,通过service对外暴漏端口,就可以通过任意node节点IP+端口访问到应用,
但是生产环境我们肯定是通过域名来访问的,这个时候就要借助Ingress-Nginx来进行代理。

在创建ingress之前,我们先创建一个pod并且暴漏端口

[root@k8smaster k8s]# kubectl get po,svc
NAME                        READY   STATUS      RESTARTS   AGE
pod/nginx-f54648c68-xmsr2   1/1     Running     1          2d2h

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/nginx        NodePort    10.111.59.136   <none>        80:32526/TCP   2d1h

10.1 创建ingress

首先下载ingress的ymal文件,官网也有,这里直接提供.
ingress-controller.yaml

使用命令创建就会创建很多东西

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

我们查看一下ingress的状态 注意ingress创建了一个叫 ingress-nginx的名称空间

[root@k8smaster k8s]# kubectl get po -n ingress-nginx
NAME                                       READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-766fb9f77-85jqd   1/1     Running   0          54s

10.2 创建ingress的规则

我们把ingress和刚刚创建的service关联起来
host就是我们要绑定的域名,这里我先用本地host验证以下
下边的serviceName就是我们创建的nginx对外暴露端口的service名称
servicePort就是pod的内部端口

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
  - host: example.nginx.com
    http:
      paths:
      - path: /
        backend:
          serviceName: nginx
          servicePort: 80

创建ingress并查看

[root@k8smaster k8s]# kubectl apply -f ingress.yaml 
ingress.networking.k8s.io/example-ingress created
[root@k8smaster k8s]# kubectl get ing
NAME              CLASS    HOSTS               ADDRESS   PORTS   AGE
example-ingress   <none>   example.nginx.com             80      7s

创建之后,我们要通过ingress来访问我们的nginx服务,我们先看下ingress部署到哪台node

[root@k8smaster k8s]# kubectl get po -n ingress-nginx -o wide
NAME                                       READY   STATUS    RESTARTS   AGE     IP                NODE       NOMINATED NODE   READINESS GATES
nginx-ingress-controller-766fb9f77-85jqd   1/1     Running   0          7m45s   192.168.182.130   k8snode2   <none>           <none>

可以看到是在k8snode2节点上,对应的IP是192.168.182.130, 我们在本地host做一下映射测试一下
image.png
然后访问我们映射的域名
image.png
到这里,我们就可以通过ingress来部署域名进行服务的映射了~

参考资料

NameUrl
Taints污点与Tolerations容忍https://www.cnblogs.com/zhanglianghhh/p/14022018.html
使用yaml文件创建service向外暴露服务https://blog.csdn.net/wucong60/article/details/81699196
名词解释:StatefulSethttps://www.kubernetes.org.cn/statefulset