kubernetes(三)

k8s之持久化存储pv&pvc

存储资源管理

在基于k8s容器云平台上,对存储资源的使用需求通常包括以下几方面:

1.应用配置文件、密钥的管理;
2.应用的数据持久化存储;
3.在不同的应用间共享数据存储;

k8s支持Volume类型

k8s的Volume抽象概念就是针对以上问题提供的解决方案,k8s的volume类型非常丰富,从临时目录、宿主机目录、ConfigMap、Secret、共享存储(PV和PVC)。

1.临时空目录(随着Pod的销毁而销毁)
emptyDir
2.配置类(将配置文件以Volume的形式挂载到容器内)
ConfigMap:将保存在ConfigMap资源对象中的配置文件信息挂载到容器内的某个目录下
Secret:将保存在Secret资源对象中的密码密钥等信息挂载到容器内的某个文件中。
downwardAPI:将downwardAPI的数据以环境变量或文件的形式注入容器中。
3.本地存储类
hostpath:将宿主机的目录或文件挂载到容器内进行使用。
4.共享存储
PV(Persistent Volume):将共享存储定义为一种“持久存储卷”,可以被多个容器应用共享使用。
PVC(Persistent Volume Claim):用户对存储资源的一次“申请”,PVC申请的对象是PV,一旦申请
成功,应用就能够像使用本地目录一样使用共享存储了。

ConfigMap、Secret、emptyDir、hostPath等属于临时性存储,当pod被调度到某个节点上时,它们随pod的创建而创建,临时占用节点存储资源,当pod离开节点时,存储资源被交还给节点,pod一旦离开这个节点,存储就失效,不具备持久化存储数据的能力。与此相反,持久化存储拥有独立的生命周期,具备持久化存储能力,其后端一般是独立的存储系统如NFS、iSCSI、cephfs、glusterfs等。

pv与pvc

官方文档持久卷 | Kubernetes

1、PV 概念

PersistentVolume(PV)持久卷
是集群中的一块存储,可以由管理员配置或者使用存储类(Storage Class)来动态配置。
持久卷是集群资源。PV持久卷和普通的 Volume 一样,也是使用卷插件来实现的,pv是对诸如NFS、
iSCSI、云存储等各种存储为后端所提供的存储(如:可以从远程的NFS 或分布式对象
存储系统中创建出PV存储空间大小、访问方式)。
PV拥有完全独立的生命周期。
PV 就是从存储设备中的空间创建出一个存储资源。

​2、PVC概念

PersistentVolumeClaim(PVC)持久卷申领
PVC用户对存储的请求。概念上与 Pod 类似。Pod会耗用节点资源,而PVC申领会耗用PV资源。Pod
可以请求特定数量的资源(CPU 和内存)。同样 PVC 申领也可以请求特定的大小和访问模式.
不需要指定具体的PV,通过标签选择器来动态匹配PV。只有匹配成功的PV才能被绑定到PVC。一旦
绑定成功,Pod通过PVC访问PV提供的存储资源。
用户不能直接使用PV,需要通过pvc先向系统申请存储空间,PVC的存在使得Pod与具体的存储实现
解耦,提高了可移植性。PVC申领耗用PV资源。

PVC 的使用逻辑:在pod中定义一个存储卷(该存储卷类型为PVC),PVC必须与对应的PV建立关系,
PVC会根据配置定义的时候定义申请的空间大小与访问模式去向PV申请存储空间。

pv对比pvc

PV是集群中的资源。 PVC是对这些资源的请求。
PV 和 PVC 可以将 pod 和数据卷解耦,pod 不需要知道确切的文件系统或者支持它的持久化引擎。

PV和PVC的生命周期

PV 和 PVC 之间的相互作用遵循这个生命周期:
Provisioning(配置) ---> Binding(绑定) ---> Using(使用) ---> Releasing(释放) 
---> Recycling(回收)
(1)创建Provisioning
PV可以由集群管理员手动创建(静态方式),或通过动态配置的方式由StorageClass自动创建。
(2)绑定Binding
PV可以绑定到一个或多个PVC。当PVC被创建并与PV匹配时,它们将被绑定在一起,形成PV-PVC绑定关系。
(3)使用Using
PV可以被Pod使用,Pod可以通过VolumeMount将PV挂载到容器中,并在容器中访问持久化存储。
Pod 通过 PVC 使用该Volume,并可以通过准入控制StorageProtection(1.9及以前版本为
PVCProtection) 阻止删除正在使用的PVC
​(4)释放Releasing
Pod 释放 Volume 并删除 PVC
在释放后,PV可以重新被其他PVC绑定,以供新的Pod使用
(5)回收Reclaiming
回收 PV,保留 PV 以便下次使用,或直接从云存储中删除释放后对PV进行清理和重用的过程。
回收策略:决定PV在释放后的处理方式,包含:
Retain(保留):删除PV或手动清理PV数据,位于外部基础设施中的存储资产在PV删除之后仍然存在
Delete(删除):移除PV并从外部基础设施中移除所关联的存储资产,动态模式的默认策略
Recycle(回收):已弃用
​
一个PV从创建到销毁的具体流程:
一个PV创建完后状态会变成Available,等待被PVC绑定。
一旦被PVC邦定,PV的状态会变成Bound,就可以被定义了相应PVC的Pod使用。
Pod使用完后会释放PV,PV的状态变成Released。
变成Released的PV会根据定义的回收策略做相应的回收工作。

Kubernetes的共享存储供应模式(PV的模式)

包括静态和动态两种模式

静态模式
静态PV由系统管理员负责创建、提供、维护,系统管理员为用户屏蔽真正提供存储的后端及其实现细节,
普通用户作为消费者,只需通过PVC申请、使用此类资源。需要先创建好PV,然后在将定义好PVC进行
一对一的Bond
​
动态模式:
如果PVC请求成千上万,需要创建成千上万的PV,对于运维人员来说维护成本很高。为了解决这一问题
K8s提供一种自动创建PV的机制,叫StorageClass,它的作用就是自动创建PV的模板。通过创建StorageClass
定义 PV 的属性,比如存储类型、大小等
创建这种 PV 需要用到的存储插件。有了这两部分信息,Kubernetes 就能够根据用户提交的 PVC,
自动找到对应的 StorageClass,然后 Kubernetes 就会调用 StorageClass 声明的存储插件,自动
创建需要的 PV 并进行绑定。如果用户的PVC中“storage class”的值为"",则表示不能为此PVC动态创建PV。

访问模式

定义了存储卷如何被集群中的节点挂载。访问模式并不直接决定Pod对存储的访问权限,而是决定PV支持哪些节点挂载方式。PV的访问模式主要有以下几种:

ReadWriteOnce   卷被一个节点以读写方式挂载。可以在同一节点上运行的多个Pod访问该卷。 
ReadOnlyMany    卷可以被多个节点以只读方式挂载。
ReadWriteMany   卷可以被多个节点以读写方式挂载。
ReadWriteOncePod  卷可以被单个 Pod 以读写方式挂载。

在命令行接口(CLI)中,访问模式也使用以下缩写形式:
RWO - ReadWriteOnce
ROX - ReadOnlyMany
RWX - ReadWriteMany
RWOP - ReadWriteOncePod

​PV与PVC的绑定

用户创建包含容量、访问模式等信息的PVC,向系统请求存储资源。系统查找已存在PV或者监控新
创建PV,如果与PVC匹配则将两者绑定。
如果PVC创建动态PV,则系统将一直将两者绑定。PV与PVC的绑定是一一对应关系,不能重复绑定。
如果系统一直没有为PVC找到匹配PV,则PVC会一直处在pending状态,直到系统找到匹配PV。实际
绑定的PV容量可能大于PVC中申请的容量。

持久卷pv的类型

PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件:

  • csi - 容器存储接口(CSI)
  • fc - Fibre Channel(FC)存储
  • hostPath - HostPath 卷 (仅供单节点测试使用;不适用于多节点集群;请尝试使用 local 卷作为替代)
  • iscsi - iSCSI(IP 上的 SCSI)存储
  • local - 节点上挂载的本地存储设备
  • nfs - 网络文件系统(NFS)存储

实验mysql基于NFS共享存储实现静态持久化存储

安装NFS

k8s-master 制作nfs服务端作为共享文件系统

[root@k8s-master ~]# yum install -y nfs-utils rpcbind
[root@k8s-master ~]# mkdir /mnt/data  #制作共享目录
[root@k8s-master ~]# vim /etc/exports
/mnt/data 192.168.122.0/24(rw,no_root_squash)
[root@k8s-master ~]# systemctl start rpcbind  #启动服务
[root@k8s-master ~]# systemctl start nfs
​
集群中的工作节点都需要安装客户端工具,主要作用是节点能够驱动 nfs 文件系统
只安装,不启动服务
# yum install -y nfs-utils

制作PV

master节点制作pv.yaml(一般这个动作由 kubernetes 管理员完成,也就是我们运维人员)

[root@k8s-master ~]# mkdir /k8s/mysql -p 
[root@k8s-master ~]# cd /k8s/mysql/
[root@k8s-master mysql]# vim pv.yaml
apiVersion: v1
kind: PersistentVolume   #类型定义为pv
metadata: 
  name: my-pv  #pv的名字
  labels:  #定义标签
    type: nfs
spec:
  storageClassName: nfs # 存储类型,需要与底层实际的存储一致,这里采用 nfs
  nfs:  # 定义nfs的配置信息
    server: 192.168.122.24 # NFS 服务器的 IP
    path: "/mnt/data" # NFS 上共享的目录
  capacity:   #定义存储能力
    storage: 3Gi  #指定存储空间
  accessModes:  #定义访问模式
    - ReadWriteMany   #读写权限,允许被多个Node挂载
  persistentVolumeReclaimPolicy: Retain  #定义数据回收策略,这里是保留

PV参数详解

1.存储能力(Capacity)

描述存储设备的能力,目前仅支持对存储空间的设置(storage=xx)。

2.访问模式(Access Modes)

对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限。访问模式如下:

ReadWriteOnce:读写权限,并且只能被单个pod挂载。
ReadOnlyMany:只读权限,允许被多个pod挂载。
ReadWriteMany:读写权限,允许被多个pod挂载。
某些PV可能支持多种访问模式,但PV在挂载时只能使用一种访问模式,多种访问模式不能同时生效。

3.persistentVolumeReclaimPolicy定义数据回收策略

目前支持如下三种回收策略:

1.保留(Retain):该策略表示保留PV中的数据,不进行回收,必须手动处理。
2.回收空间(Recycle):该策略表示在PV释放后自动执行清除操作,使PV可以重新使用。效果相当于
执行 rm -rf /thevolume/*。只有 NFS 和 HostPath 两种类型的 PV支持 Recycle 策略。
3.删除(Delete):该策略表示在PV释放后自动删除PV中的数据。同时也会从外部(如 AWS EBS、
GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 就是把云存储一起删了。

4.storageClassName存储类别(Class)

PV可以设定其存储的类型(Class),通过 storageClassName参数指定一个 StorageClass
资源对象的名称。具有特定“类别”的 PV 只能与请求了该“类别”的 PVC 进行绑定。未设定 “类别” 
的 PV 则只能与不请求任何 “类别” 的 PVC 进行绑定。
相当于一个标签。
创建pv
[root@k8s-master mysql]# kubectl apply -f pv.yaml 
persistentvolume/my-pv created
[root@k8s-master mysql]# kubectl get pv

PV 生命周期的各个阶段(Phase)
某个 PV 在生命周期中,可以处于以下4个阶段之一:
- Available:可用状态,还未与某个 PVC 绑定。
- Bound:已与某个 PVC 绑定。
- Released:释放,绑定的 PVC 已经删除,但没有被集群回收存储空间 。
- Failed:自动资源回收失败。

Access mode解释:
RWO:readwariteonce: 允许同一个node节点上的pod已读写的方式访问
ROX: readonlymany:   允许不同node节点的pod以只读方式访问
RWX: readwritemany:  允许不同的node节点的pod以读写方式访问

pv创建成功目前属于可用状态,还没有与pvc绑定,那么现在创建pvc

创建pvc

[root@k8s-master mysql]# vim mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim   #定义类型为PVC
metadata:
  name: mypvc   #声明pvc的名称,当做pod的卷使用时会用到
spec:
  accessModes:  #定义访问pvc的模式,与pv拥有一样的模式
    - ReadWriteMany   #读写权限,允许被多个pod挂载
  resources:  #声明可以请求特定数量的资源,目前仅支持 request.storage 的设置,即存储空间大小。
   requests:
     storage: 3Gi  #定义空间大小
  storageClassName: nfs #指定绑定pv的类型,即使不指定类型pvc也会自动去匹配对应大小的pv
       
​
注意:当我们申请pvc的容量大于pv的容量是无法进行绑定的。
​
创建pvc
[root@k8s-master mysql]# kubectl apply -f mysql-pvc.yaml 
persistentvolumeclaim/mypvc created
[root@k8s-master mysql]# kubectl get pvc
NAME    STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc   Bound    my-pv    3Gi        RWX            pv-nfs         37s

status状态

- Available (可用): 表示可用状态,还未被任何PVC绑定
- Bound (已绑定):已经绑定到某个PVC
- Released (已释放):对应的PVC已经删除,但资源还没有被集群收回
- Failed:PV自动回收失败

Kubernetes中会自动帮我们查看pv状态为Available并且根据声明pvc容量storage的大小进行筛选匹配,同时还会根据AccessMode进行匹配。如果pvc匹配不到pv会一直处于pending状态。

mysql使用pvc持久卷

创建secret
[root@k8s-master mysql]# echo -n 'Yunjisuan@666' | base64
WXVuamlzdWFuQDY2Ng==
[root@k8s-master mysql]# vim mysql-secret.yaml
apiVersion: v1
data:
  password: WXVuamlzdWFuQDY2Ng==
kind: Secret
metadata:
  annotations:
  name: my-pass
type: Opaque
[root@k8s-master mysql]# kubectl apply -f mysql-secret.yaml 
secret/my-pass created
[root@k8s-master mysql]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-24c52   kubernetes.io/service-account-token   3      6d22h
my-pass               Opaque                                1      69s
创建myslq-pod文件
[root@k8s-master mysql]# vim mysql-pv.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: my-mysql
  labels:
    db: mysql
spec:
  containers:
  - name: my-mysql
    image: daocloud.io/library/mysql:5.7
    ports:
    - containerPort: 3306
    env:
    - name: MYSQL_ROOT_PASSWORD
      valueFrom:
        secretKeyRef:
           name: my-pass
           key: password
    volumeMounts:
    - name: mysql-data
      mountPath: /var/lib/mysql
 volumes:
 - name: mysql-data
   persistentVolumeClaim: #定义卷的类型为pvc
   claimName: mypvc #指定对应pvc的名字
[root@k8s-master mysql]# kubectl apply -f mysql-pv.yaml          
[root@k8s-master mysql]# kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
my-mysql    1/1     Running   0          8s
[root@k8s-master mysql]# kubectl get pod -o wide
[root@master mysql]# kubectl get pod -l db=mysql -o wide 
NAME       READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
my-mysql   1/1     Running   0          24s   10.244.2.80   node-2   <none>           <none>
测试
[root@k8s-master ~]# cd /mnt/data/
[root@k8s-master data]# ll
总用量 188484
-rw-r----- 1 polkitd ssh_keys       56 11月  8 21:49 auto.cnf
-rw------- 1 polkitd ssh_keys     1680 11月  8 21:49 ca-key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月  8 21:49 ca.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月  8 21:49 client-cert.pem
-rw------- 1 polkitd ssh_keys     1680 11月  8 21:49 client-key.pem
-rw-r----- 1 polkitd ssh_keys      688 11月  8 21:57 ib_buffer_pool
-rw-r----- 1 polkitd ssh_keys 79691776 11月  8 21:59 ibdata1
-rw-r----- 1 polkitd ssh_keys 50331648 11月  8 21:59 ib_logfile0
-rw-r----- 1 polkitd ssh_keys 50331648 11月  8 21:49 ib_logfile1
-rw-r----- 1 polkitd ssh_keys 12582912 11月  8 22:00 ibtmp1
drwxr-x--- 2 polkitd ssh_keys     4096 11月  8 21:49 mysql
drwxr-x--- 2 polkitd ssh_keys     8192 11月  8 21:49 performance_schema
-rw------- 1 polkitd ssh_keys     1680 11月  8 21:49 private_key.pem
-rw-r--r-- 1 polkitd ssh_keys      452 11月  8 21:49 public_key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 11月  8 21:49 server-cert.pem
-rw------- 1 polkitd ssh_keys     1676 11月  8 21:49 server-key.pem
drwxr-x--- 2 polkitd ssh_keys     8192 11月  8 21:49 sys

动态绑定pv

StorageClass 相当于一个创建 PV 的模板,用户通过 PVC 申请存储卷,StorageClass 通过模板自动创建 PV,然后和 PVC 进行绑定。

StorageClass创建动态存储卷流程

1)集群管理员预先创建存储类(StorageClass);
2)用户创建使用存储类的持久化存储声明(PVC:PersistentVolumeClaim);
3)存储持久化声明通知系统,它需要一个持久化存储(PV: PersistentVolume);
4)系统读取存储类的信息;
​5)系统基于存储类的信息,在后台自动创建PVC需要的PV;
​6)用户创建一个使用PVC的Pod;
​7)Pod中的应用通过PVC进行数据的持久化;
​8)而PVC使用PV进行数据的最终持久化处理。

StorageClass支持的动态存储插件:

面试点

PV 和 PVC 有什么区别?

  • PV 是由集群管理员创建的存储资源,它定义了存储的容量、访问模式、回收策略等属性。
  • PVC 是用户向集群申请存储资源的方式,它指定了所需的存储容量和访问模式。

PV 和 PVC 的生命周期有哪些阶段?

PV 的生命周期包括:
Available:PV 已创建,但尚未绑定到 PVC。
Bound:PV 已经绑定到 PVC。
Released:PV 与 PVC 解绑。
Recycling:PV 正在被清理。
Failed:PV 处于故障状态。

PVC 的生命周期包括:
Pending:PVC 已创建,但尚未绑定到 PV。
Bound:PVC 已经绑定到 PV。
Lost:PVC 与 PV 之间的绑定丢失。

dnsPolicy

在 Kubernetes 中,`dnsPolicy` 是一个重要的配置选项,它影响 Pod 如何进行 DNS 解析。以下是 Kubernetes 中支持的几种 `dnsPolicy` 选项:

1. ClusterFirst
   - 这是默认的 DNS 策略,意味着当 Pod 需要进行域名解析时,首先会查询集群内部的 CoreDNS 服务。通过 CoreDNS 来做域名解析,表示 Pod 的 `/etc/resolv.conf` 文件被自动配置指向 kube-dns 服务地址。如果集群内的 DNS 服务器不能解析该域名,查询将会被转发到节点的上游 DNS 服务器 。

2. Default
   - 当设置为 `Default` 时,Pod 会继承运行所在节点的名称解析配置。这意味着 Pod 的 DNS 配置与宿主机完全一致。请注意,“Default” 不是默认的 DNS 策略;如果没有明确指定 `dnsPolicy`,则默认使用 “ClusterFirst” 。

3. None
   - 使用 `None` 策略时,Kubernetes 会忽略集群的 DNS 策略,Pod 将不会收到任何预设的 DNS 配置。为了确保 Pod 仍然可以解析域名,你需要通过 `dnsConfig` 字段来指定自定义的 DNS 配置信息。例如,你可以指定名称服务器(`nameservers`)、搜索路径(`searches`)以及其他选项(`options`) 。

4. ClusterFirstWithHostNet
   - 这种策略适用于使用主机网络(`hostNetwork`)的 Pod。在这种情况下,Pod 会首先使用集群内的 DNS 服务器进行解析,然后才会使用主机网络的 DNS 服务器。当 Pod 使用主机网络模式时,应该显式设置其 DNS 策略为 `ClusterFirstWithHostNet` 。

以下是具体的使用示例:

- 使用 `ClusterFirst` 策略:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  containers:
  - name: example-container
    image: nginx
  dnsPolicy: ClusterFirst

- 使用 `None` 策略,并自定义 DNS 配置:

apiVersion: v1
kind: Pod
metadata:
  name: my-custom-dns-pod
spec:
  containers:
  - name: my-container
    image: my-image
  dnsPolicy: None
  dnsConfig:
    nameservers:
    - 1.1.1.1
    - 8.8.8.8
    searches:
    - my-domain.com
    - ns1.my-domain.com
    options:
    - name: ndots
      value: "2"

在这个例子中,dnsPolicy被设置为None,这意味着Pod将不使用Kubernetes的默认DNS设置,而是使用dnsConfig中定义的配置。nameservers指定了DNS服务器的地址,searches定义了搜索域,options定义了其他的选项。

Statefulset有状态服务控制器

StatefulSet | Kubernetes

StatefulSet 是 Kubernetes 中用于管理有状态应用的工作负载控制器。与 Deployment 这类主要用于无状态应用的控制器不同,StatefulSet 设计用于处理那些需要持久化存储、稳定唯一网络标识符以及有序滚动更新的应用场景,例如数据库集群、消息队列、文件服务器等。

Service 管理

  • Headless Service: 通常会创建一个 Headless Service(无头服务),用来提供 Pod 的 DNS 名称,而不需要为 Service 分配一个 Cluster IP。
  • Service Selector: 通过标签选择器关联 Pod 和 Service。
Pod 的标识
  • 序号索引:对于具有 N 个副本的 StatefulSet,每个 Pod 都会被分配一个从 00 到 N−1 的唯一整数序号。
  • Pod 命名:每个 Pod 的名称形式为 <statefulSetName>-<ordinal>,其中 <ordinal> 是从 00 开始的序号。例如,如果有一个名为 my-statefulset 的 StatefulSet,它创建的 Pod 可能命名为 my-statefulset-0my-statefulset-1 等等。
网络标识
  • Headless Service:StatefulSet 通常会创建一个没有 ClusterIP 的 Service(称为 Headless Service 或无头服务),这个 Service 不会为 StatefulSet 中的 Pods 分配集群 IP 地址,而是直接暴露 Pods 的 IP 地址。这意味着你可以直接访问这些 Pods,或者通过 DNS 解析它们的名字来访问它们。
  • DNS 记录:Pod 会根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。组合主机名的格式为 $(StatefulSet 名称)-$(序号)。例如,一个名为 web 的 StatefulSet 将会创建名称分别为 web-0web-1web-2 的 Pods。

Pod 的创建和删除顺序

  • 创建顺序:当 StatefulSet 部署 Pod 时,会从编号 00 到最终编号逐一部署每个 Pod,只有前一个 Pod 部署完成并处于运行状态后,下一个 Pod 才会开始部署。
  • 删除顺序:在删除或缩容时,StatefulSet 会从最高序号到最低序号依次删除 Pod

k8s有状态服务和无状态服务的区别

无状态服务特点

1、无状态服务不会在本地存储持久化数据.多个实例可共享相同的持久化数据,如nginx/tomcat/负载
均衡等实例。
2、在k8s中启停无状态服务的pod不影响其它pod,而且pod的名字和网络也都是随机生成。
3、管理无状态服务的资源有ReplicaSet、ReplicationController、Deployment
4、随机缩容:创建的pod都是随机的,缩容也是随机的,并不会明确缩容某一个pod,所以缩容任何
一个pod都可以。

​有状态服务介绍

1、有状态服务需要在本地存储持久化数据,如:mysql/kafka/zookeeper/redis主从架构
2、实例之间有依赖的关系.比如,主从关系,启动顺序
3、如果停止集群中任一实例pod,就可能会导致数据丢失或者集群失败
4、创建有状态服务的方式:statefulSet管理

更新策略

StatefulSet 支持两种更新策略:RollingUpdateOnDelete

  • RollingUpdate:这种策略下,新的 Pod 会在旧的 Pod 被终止之前创建,这允许你在更新过程中保持应用的可用性。
  • OnDelete:这种策略下,只有在所有旧的 Pods 被删除后才会创建新的 Pods,这可能会导致短暂的服务中断。

StatefulSet组成部分

1. Headless Service:无头服务,用于为Pod资源标识符生成可解析的DNS记录。
2. volumeClaimTemplates:存储卷申请模板,创建pvc,指定pvc名称大小,自动创建pv,且pv由存储类供应。
3. StatefulSet:管理有状态的pod实例的

什么是Headless service?

Headless service不分配cluster IP,通过解析service的域名,返回所有Pod的endpoint列表。
​
1.headless service会为service分配一个域名
2.StatefulSet会为关联的Pod定义一个不变的Pod Name:名字格式为$(StatefulSet name)-$(pod序号)
  (pod序号从0开始)
3.StatefulSet会为关联的Pod分配一个dnsName
$<Pod Name>.$<service name>.$<namespace name>.svc.cluster.local

实验

创建pv

1.使用nfs制作pv,准备pv共享的目录
[root@k8s-master ~]# cd /data/vol/
[root@k8s-master vol]# ls
data1  data2  data3  data4  data5
2.定义nfs共享目录
[root@k8s-master ~]# cat /etc/exports
/data/vol/data1 * (rw,no_root_squash)
/data/vol/data2 * (rw,no_root_squash)
/data/vol/data3 * (rw,no_root_squash)
/data/vol/data4 * (rw,no_root_squash)
/data/vol/data5 * (rw,no_root_squash)
3.定义pv并创建pv
[root@k8s-master test-pv]# cat nfs-pv.yml 
apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-pv-1
 labels:
   app: pv-1
 namespace: default
spec:
 nfs:
  path: /data/vol/data1
  server: 172.16.229.4
 accessModes:
  - ReadWriteOnce
 capacity:
  storage: 2Gi
 storageClassName: nfs #由于是手动创建pv,在此通过该参数指定存储类型为nfs
 persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-pv-2
 labels:
   app: pv-2
 namespace: default
spec:
 nfs:
  path: /data/vol/data2
  server: 172.16.229.4
 accessModes:
  - ReadWriteOnce
 capacity:
  storage: 2Gi
 storageClassName: nfs
 persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-pv-3
 labels:
   app: pv-3
 namespace: default
spec:
 nfs:
  path: /data/vol/data3
  server: 172.16.229.4
 accessModes:
  - ReadWriteOnce
 capacity:
  storage: 2Gi
 storageClassName: nfs
 persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-pv-4
 labels:
   app: pv-4
 namespace: default
spec:
 nfs:
  path: /data/vol/data4
  server: 172.16.229.4
 accessModes:
  - ReadWriteOnce
 capacity:
  storage: 2Gi
 storageClassName: nfs
 persistentVolumeReclaimPolicy: Retain
 
[root@k8s-master test-pv]# kubectl apply -f nfs-pv.yml 
persistentvolume/nfs-pv-1 created
persistentvolume/nfs-pv-2 created
persistentvolume/nfs-pv-3 created
persistentvolume/nfs-pv-4 created
[root@k8s-master test-pv]# kubectl get pv 
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
nfs-pv-1   2Gi        RWO            Retain           Available           nfs                     4s
nfs-pv-2   2Gi        RWO            Retain           Available           nfs                     4s
nfs-pv-3   2Gi        RWO            Retain           Available           nfs                     4s
nfs-pv-4   2Gi        RWO            Retain           Available           nfs                     4s

 创建statefulset资源 

1.定义satefulset与svc资源
[root@k8s-master statefulset]# cat statefulset.yml 
apiVersion: v1
kind: Service
metadata:
 name: web-svc
 labels:
  app: web
spec:
 ports:
  - port: 80 #定义svc的端口
 clusterIP: None #定义none表示该svc没有clusterip也称为无头服务
 selector: #定义标签选择器,关联带有该标签的pod
  app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
 name: web
 namespace: default
 labels:
  app: web
spec:
 replicas: 3
 selector: #定义statefulset要管理带有某个标签的pod
  matchLabels:
   app: nginx
 serviceName: web-svc #指定管理的svc的名字
 volumeClaimTemplates: #定义持久化存储卷的申请模版
  - metadata:   #定义模版的名字
     name: www
    spec:
      accessModes: ["ReadWriteOnce"] #定义卷的访问模式
      storageClassName: nfs  #定义卷的类型,以上两个值都需要与定义的pv一致才能
                                # 自动匹配到对应的pv
      resources:
       requests:
        storage: 2Gi #定义卷的大小
 template:
  metadata:
   labels:
    app: nginx
  spec:
   containers:
    - name: nginx
      image: daocloud.io/library/nginx:1.16
      imagePullPolicy: IfNotPresent
      ports:
       - containerPort: 80
      volumeMounts:
       - name: www  #卷的名字,与卷模版的名字一样即可
         mountPath: /usr/share/nginx/html
[root@k8s-master statefulset]# kubectl apply -f statefulset.yml 
service/web-svc created
statefulset.apps/web created

[root@k8s-master ~]# kubectl get sts #sts是statefulset的缩写
NAME   READY   AGE
web    3/3     64m

[root@k8s-master statefulset]# kubectl get pod 
NAME               READY   STATUS    RESTARTS      AGE
web-0              1/1     Running   0             16s
web-1              1/1     Running   0             11s
web-2              1/1     Running   0             7s

注:pod的名字命名是由statefulset的名字加数字,按顺序命名。

[root@k8s-master statefulset]# kubectl get svc 
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   29d
web-svc      ClusterIP   None         <none>        80/TCP    27s

[root@k8s-master statefulset]# kubectl get pvc 
NAME        STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    nfs-pv-1   2Gi        RWO            nfs            5m50s
www-web-1   Bound    nfs-pv-2   2Gi        RWO            nfs            5m45s
www-web-2   Bound    nfs-pv-3   2Gi        RWO            nfs            5m41s

查看svc详细信息
[root@k8s-master statefulset]# kubectl describe svc web-svc
Name:              web-svc
Namespace:         default
Labels:            app=web
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                None
IPs:               None
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.244.247.30:80,10.244.247.31:80,10.244.84.158:80
Session Affinity:  None
Events:            <none>

[root@k8s-master statefulset]# kubectl get pod -l app=nginx -o wide 
NAME    READY   STATUS    RESTARTS   AGE     IP              NODE     NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          8m50s   10.244.247.30   node-2   <none>           <none>
web-1   1/1     Running   0          8m45s   10.244.84.158   node-1   <none>           <none>
web-2   1/1     Running   0          8m41s   10.244.247.31   node-2   <none>           <none>

进入到共享目录data1里面创建一个文件
[root@k8s-master statefulset]# cd /data/vol/data1/
[root@k8s-master data1]# echo 123123 >> index.html
访问挂载data1这个目录做成卷的pod
[root@k8s-master test-pv]# curl 10.244.247.30
123123

 使用 StatefulSet 管理的 Pod 具有的特点

1. 唯一序号分配:
   - StatefulSet 管理的 Pod 会被分配一个唯一的序号,格式为 `<statefulset名称>-<序号>`
(例如:`my-statefulset-0`, `my-statefulset-1`, `my-statefulset-2` 等)。
2. 关联 Service:
   - 创建 StatefulSet 资源时,需要预先创建一个 Service。
   - 如果创建的 Service 没有固定的 IP 地址,则可以通过 DNS 解析找到与其关联的 Pod 的 
IP 地址。
3. 有序操作:
   - StatefulSet 管理的 Pod 具有明确的顺序,在执行部署、扩展、收缩、删除及更新操作时,这些
操作将按照指定的顺序依次执行。(扩容1..N,缩容N-1..1)
   - 新创建的 Pod 名称与被删除的 Pod 名称相同,以保持一致性。
4. 持久化存储:
   - StatefulSet 包含 `volumeClaimTemplate` 字段,这是一个卷声明模板,用于自动创建 
PersistentVolume(PV)和 PersistentVolumeClaim(PVC)。
   - 自动创建的 PVC 会与 PV 绑定,确保每个 Pod 的数据目录是独立的。
   - 即使 Pod 被重新调度,也可以继续挂载原有的 PV,从而保证数据的完整性和一致性。

jenkins流水线发布项目到K8S集群实现CI/CD

大致流程

开发人员把做好的项目代码通过git推送到gitlab
然后Jenkins通过 gitlab webhook (前提是配置好),自动从拉取gitlab上面拉取代码下来。
(作用是实现本地 git push 后 jenkins 自动构建部署服务)
然后进行build,编译、生成镜像、然后把镜像推送到Harbor仓库;
然后在部署的时候通过k8s拉取Harbor上面的代码进行创建容器和服务,最终发布完成,然后可以用外网访问
​
第一阶段,获取代码(Git)
第二阶段,编译打包(Maven)
第三阶段,镜像打包与推送到仓库 (Harbor)
第四阶段,部署应用到k8s集群 (kubectl)

流程图

环境准备

192.168.116.138 master
192.168.116.130 node1
192.168.116.131 node2
192.168.116.141 harbor
192.168.116.142 jenkins  ------>充当开发角色模拟提交代码到gitlab
git仓库我们这里使用gitee代替gitlab
​
测试代码:
https://github.com/bingyue/easy-springmvc-maven

配置jenkins服务器

1.安装git客户端与docker
[root@jenkins-server ~]# yum install -y git
[root@jenkins-server ~]# yum install -y yum-utils device-mapper-persistent-data lvm2 git
[root@jenkins-server ~]# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
[root@jenkins-server ~]# yum install -y docker
启动并设置开机启动
[root@jenkins-server ~]# systemctl start docker && systemctl enable docker
2.安装jenkins---略
3.jenkins额外安装插件
docker 和pipeline 插件

指定docker环境

由于jenkins最后要将生成的镜像上传到harbor仓库,需要在jenkins上面配置登陆harbor仓库

root@jenkins-server ~]# cat /etc/docker/daemon.json
{
"insecure-registries": ["192.168.116.141"]
}
​
[root@jenkins-server ~]# systemctl restart docker 
[root@jenkins-server ~]# docker login -u admin -p Harbor12345 192.168.116.141  #测试
Login Succeeded

登陆harbor创建相应项目的仓库

更新公司代码仓库

我这里用的是gitee的仓库,同步的github上一个开源的项目代码

在git服务器上面创建公钥和私钥,将公钥上传到gitee

[root@jenkins-server ~]# ssh-keygen
[root@jenkins-server ~]# cd .ssh/
[root@jenkins-server .ssh]# ls
id_rsa  id_rsa.pub  known_hosts
[root@jenkins-server .ssh]# cat id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB。AAABAQC6rVm2pdguGz+Y72JRBk/lpbh3jfqriNWqv3QlP6iVP+tUTRZiGB5pcjCC8cHg6w64XPj4xRpQcj+Aviq7ORSuPGSk5hCMz9O7/wUMJsP4uVIvm5P/rwQBppdS3+qGE6COkhHdEY+GcjpLFVG2mW+ksQe3W2bvBKe7msNpqgUtQs+z99UXf9LlnGUCrRF/oddPSgq3tJDXZoYzdEb3a7bZDf3j72XOYubX0fcygS1fG615+xnJP9knfoKkr4YSZkxj25TwPEO5JToPtr4tcOpJRqBA7KrookicuiWeIe7J91mRVH1tx9GWnPMMyXqsy0LcyV2rUUhG4c71W0mEfLzn root@jenkins-server

将创建好的仓库克隆到本地

[root@jenkins-server ~]# git clone git@gitee.com:testpm/javaweb.git
Cloning into 'javaweb'...
warning: You appear to have cloned an empty repository.
[root@jenkins-server ~]# ls
javaweb
​
克隆测试代码,将测试代码上传到gitlab
[root@jenkins-server ~]# git clone https://github.com/bingyue/easy-springmvc-maven
[root@jenkins-server ~]# cd easy-springmvc-maven/
[root@jenkins-server easy-springmvc-maven]# ls
pom.xml  README.md  src
[root@jenkins-server easy-springmvc-maven]# cp -r * /root/javaweb/
[root@jenkins-server easy-springmvc-maven]# ls /root/javaweb/
pom.xml  README.md  src
​
将代码上传到公司的远程仓库
[root@jenkins-server easy-springmvc-maven]# cd /root/javaweb/
[root@jenkins-server javaweb]# git add .
[root@jenkins-server javaweb]# git commit -m "1.0"
[root@jenkins-server javaweb]# git push origin master

编写dockerfile用于生成镜像
[root@jenkins-server javaweb]# cat Dockerfile 
FROM daocloud.io/library/tomcat:8.0.45
RUN rm -rf /usr/local/tomcat/webapps/*
ADD ./target/easy-springmvc-maven.war /usr/local/tomcat/webapps/
EXPOSE 8080
ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh","run"]
​
重新上传到远程仓库
[root@jenkins-server javaweb]# git add -A 
[root@jenkins-server javaweb]# git commit -m "v2.0"
[root@jenkins-server javaweb]# git push origin master

配置jenkins流水线进行打包编译生成镜像并推送到公司的harbor仓库

基本配置略,开始编写pipeline脚本

root@jenkins-server ~]# cat /root/.ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAuq1ZtqXYLhs/mO9iUQZP5aW4d436q4jVqr90JT+olT/rVE0W
YhgeaXIwgvHB4OsOuFz4+MUaUHI/gL4quzkUrjxkpOYQjM/Tu/8FDCbD+LlSL5uT
/68EAaaXUt/qhhOgjpIR3RGPhnI6SxVRtplvpLEHt1tm7wSnu5rDaaoFLULPs/fV
F3/S5ZxlAq0Rf6HXT0oKt7SQ12aGM3RG92u22Q394+9lzmLm19H3MoEtXxutefsZ
yT/ZJ36CpK+GEmZMY9uU8DxDuSU6D7a+LXDqSUagQOyq6KJInLolniHuyfdZkVR9
bcfRlpzzDMl6rMtC3Mldq1FIRuHO9VtJhHy85wIDAQABAoIBAHKhDezVK8ksHEJa
BKJCYP9gYsSvH1UDwkiGJdjVnTyAso3ihDMaIlqXruhJccceL7zQAmYoCj+J2CrD
G1Q5+dP/68FCMLl2yMqxDzVl/IKXsbrgKuIfYzNsS5GtQ8Ku/LFvSp7YMKzbKaZ/
YdzhnSehOV7DNpLg5eqSXxpcN6+RQAhnpjNDqRAFNsUhJ5jpSx1gUDEVAGM+4K6l
Vt6tK+8LuPw31dKkh6I+PYOQAlLfRBI5uk9szXM+cRY0/45rJfUX6K8PFFn4l8go
hz2lVxZAJYEkyYVGslAHBuFFcR9pU/29YfZEqGSFUksypbtac4MnXzo4K30pbJuS
vq0S4akCgYEA8+2AOrafZ7vbmD4fTb413sXyOTq/lfH4Y/V54tUrGNUIzpYSzV4O
u+6GpwhSDxspupoMuvDj3J1Zr24dq1kNfm1cVWfwY2eQE/VoZSCCB33abfqxKR7x
SX5KNNPoxUIF+w2biWW6aGY2JJFyeNt/BifY8zq4wrbZ3/JneHIzDnMCgYEAw+p/
+8PUEbLz/BMzHfptMsu/GqDieM4QMjMMlNLRYpSG58PFQwJVFPdQv04l2BZziUeL
0SUe/p6XBaEO642yeMhlrUw75nhEMH+w/Ab+ivwuUi/6g+Fd8gDn2+w8Q5nNe3Ow
gdwuN/N3zidwufwylsk9K2HfgouUHsqr171CJr0CgYAzisPFpEEakj9cdAP5UW/g
msMMBLXm4TIzLPOMUq7AmIM748olSvKiE0ywjrmIJ50xqMt31N7RBw6kWwg55J9N
T55rDYNl/cQb84cTl0Ligl1dT9OPdNJXTTAw6XYN+F17Juzuo2g4FyDpqTIfB4JX
sqPNAWN5AVoYUAg9EyRmsQKBgQC/TzICq0terRzLPgRgdbZEL+lBG3GK/c2a93aF
rQeB9/90OhtaP+DNRGO5K5qQ0/umRhMl+9W0VTCr8oYbRpbMkwOWoiar+yy7HzW4
JSxSk8a3wtoiBeQy+OdwPdLrQDkDIAlwBY2sXTbUPHc7ZiYQUBpHTp9vXtUY7Fe5
p7n9EQKBgQCgf6HN6JQmTSAjcPd0HFmzpe54PO+5XGjGnUSxqo4ZeOVklRi+b+vn
QeeVyItMi2oyt5cNnxL/oSKqXgumKXOAL37x0eLJHLIWX3iHISvS0rIpSkvWEDrD
iPj7001u4uBDz1/ivlTpfdpmx5otyTJloP4JRIOUAZsY4WxsORtIOg==
-----END RSA PRIVATE KEY-----

复制脚本内容返回上一级页面添加到脚本中

最后完整脚本内容:

pipeline {
    agent any
​
    stages {
        stage('pull code') {
            steps {
                checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: 'da64354c-fffc-4fbf-9865-bf9c74e25b95', url: 'git@gitee.com:testpm/javaweb.git']]])
            }
        }
        stage('build code') {
            steps {
                sh 'cd /root/.jenkins/workspace/test-web1'
                sh 'mvn clean package'
            }
        }
        stage('build image'){
            steps {
                sh 'docker build -t javaweb:v2.0 .'
            }
        }
        stage('push image to harbor') {
            steps {
                sh 'docker login -u admin -p Harbor12345 192.168.116.141'
                sh 'docker tag javaweb:v2.0 192.168.116.141/javapm/javaweb:v2.0'
                sh 'docker push 192.168.116.141/javapm/javaweb:v2.0 && docker rmi 192.168.116.141/javapm/javaweb:v2.0'
            }
        }
    }
}
​

登陆harbor仓库查看镜像是否上传成功

发布项目到k8s集群,编写deployment进行发布

1.k8s集群配置登陆harbor仓库的secret
2.开始编写yaml文件
[root@master ~]# cd yaml/
[root@master yaml]# cat java-dep.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
 name: tomcat-dep
spec:
 selector:
  matchLabels:
   app: web
 replicas: 2
 template:
  metadata:
   labels:
    app: web
  spec:
   volumes:
    - name: log-vol
      hostPath:
       path: /var/javaweb/log
   containers:
    - name: tomcat
      image: 192.168.116.141/javapm/javaweb:v2.0
      ports:
       - containerPort: 8080
      volumeMounts:
       - mountPath: "/usr/local/tomcat/logs"
         name: log-vol
   imagePullSecrets:
    - name: harbor-login
    
创建deployment
[root@master yaml]# kubectl apply -f java-dep.yaml 
deployment.apps/tomcat-dep created
[root@master yaml]# kubectl get pod 
NAME                          READY   STATUS    RESTARTS        AGE
nginx-web                     1/1     Running   1 (6h14m ago)   14h
tomcat-dep-757df55fb5-2brjh   1/1     Running   0               40s
tomcat-dep-757df55fb5-5ppfw   1/1     Running   0               40s

配置Ingress实现访问

下载ingress配置文件: 如果下载不下来记得添加解析
[root@master ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.116.138 master
192.168.116.130 node1
192.168.116.131 node2
199.232.28.133 raw.githubusercontent.com  #解析github的地址
​
[root@master ~]# cd /mnt/
[root@master mnt]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.3.0/deploy/static/provider/cloud/deploy.yaml
[root@master mnt]# cd
[root@master ~]# vim deploy.yaml #修改配置文件,并替换成阿里云的镜像
找到已下apiserver的版本:
---
apiVersion: apps/v1
kind: DaemonSet  #将原来的Deployment修改为DaemonSet
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.3.0
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  minReadySeconds: 0
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/component: controller
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/name: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
    spec:
      hostNetwork: true  #添加共享到主机网络
      containers:
      - args:
        - /nginx-ingress-controller
        - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
        - --election-id=ingress-controller-leader
        - --controller-class=k8s.io/ingress-nginx
        - --ingress-class=nginx
        - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
        - --validating-webhook=:8443
        - --validating-webhook-certificate=/usr/local/certificates/cert
        - --validating-webhook-key=/usr/local/certificates/key
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: LD_PRELOAD
          value: /usr/local/lib/libmimalloc.so
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.3.0@sha256:d1707ca76d3b044ab8a28277a2466a02100ee9f58a86af1535a3edf9323ea1b5 #将原来的镜像替换成阿里云仓库的镜像
        imagePullPolicy: IfNotPresent
        lifecycle:
          preStop:
            exec:
              command:
              - /wait-shutdown
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: controller
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
        - containerPort: 8443
          name: webhook
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          requests:
            cpu: 100m
            memory: 90Mi
        securityContext:
          allowPrivilegeEscalation: true
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - ALL
          runAsUser: 101
        volumeMounts:
        - mountPath: /usr/local/certificates/
          name: webhook-cert
          readOnly: true
      dnsPolicy: ClusterFirst
      nodeSelector:
        custom/ingress-controller-ready: "true"  #指定运行ingress的node标签
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
      - name: webhook-cert
        secret:
          secretName: ingress-nginx-admission
---
​
#注意:一共有两个镜像需要替换成阿里云仓库的镜像:
[root@master ~]# cat deploy.yaml | grep image
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.3.0@sha256:d1707ca76d3b044ab8a28277a2466a02100ee9f58a86af1535a3edf9323ea1b5
        imagePullPolicy: IfNotPresent
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660
        imagePullPolicy: IfNotPresent
        image: registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.1.1@sha256:64d8c73dca984af206adf9d6d7e46aa550362b1d7a01f3a0a91b20cc67868660
        imagePullPolicy: IfNotPresent
给两个node节点设置lable
[root@master ~]# kubectl label nodes node1 custom/ingress-controller-ready=true
node/node1 labeled
[root@master ~]# kubectl label nodes node2 custom/ingress-controller-ready=true
node/node2 labeled
​
同时在两个node节点下载镜像
[root@node1 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.3.0
[root@node1 ~]# docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.1.1
创建ingress-controller
[root@master ~]# kubectl apply -f deploy.yaml
​
查看ingress-controller资源
[root@master ~]# kubectl get pods -n ingress-nginx
NAME                             READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-s8vnl   1/1     Running   0          98m
nginx-ingress-controller-ztxz4   1/1     Running   0          97m

测试ingress

创建service

[root@master ~]# cd yaml/
[root@master yaml]# vim java-service.yaml
apiVersion: v1
kind: Service
metadata:
 name: my-tomcat
 labels:
  run: my-tomcat
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
   app: web
  
创建service
root@master yaml]# kubectl apply -f java-service.yaml 
service/my-tomcat created
​
查看资源
[root@master yaml]# kubectl get pods
NAME                          READY   STATUS    RESTARTS        AGE
nginx-web                     1/1     Running   1 (7h10m ago)   15h
tomcat-dep-757df55fb5-2brjh   1/1     Running   0               57m
tomcat-dep-757df55fb5-5ppfw   1/1     Running   0               57m
[root@master yaml]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP   26h
my-tomcat    ClusterIP   10.109.127.156   <none>        80/TCP    29s

配置ingress转发文件

[root@master ~]# cat ingress-test.yaml 
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: test-ingress
 namespace: default
 annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /easy-springmvc-maven/
spec:
 ingressClassName: nginx #指定ingress的类型
 rules:  #定义转发规则
 - host: test.tomcat.ingress  #指定域名方式
   http:
    paths:
    - path: /easy-springmvc-maven/  #指定访问的路径
      pathType: Prefix  #定义路径的类型
      backend:   #定义转发后端的服务
       service:  #定义转发的service
         name: my-tomcat
         port:
          number: 80
          
[root@master yaml]# kubectl apply -f ingress-test.yaml 
ingress.networking.k8s.io/test-ingress created
[root@master yaml]# kubectl get ingress
NAME           CLASS    HOSTS                 ADDRESS   PORTS   AGE
test-ingress   <none>   test.tomcat.ingress             80      20s

nginx-ingress-controller运行在node1,node2两个节点上。

如果网络中有dns服务器,在dns中把这两个域名映射到nginx-ingress-controller运行的任意一个节点上,如果没有dns服务器只能修改host文件了。

任意一个节点上操作:(客户端解析)

我这里有两个节点部署了控制器,ip分别为172.16.229.5,172.16.229.6 ,如果有多个,可以随便选。(但是由于tomcat都在node2上面,所以就只解析一台)

在wind电脑设置本地解析

192.168.116.131 test.tomcat.ingress

测试访问

DeamonSet详解

1、何为DaemonSet

介绍DaemonSet我们先来思考一个问题:相信大家都接触过监控系统比如zabbix,监控系统需要在被监控机安装一个agent,安装agent通常会涉及到以下几个场景:

- 所有节点都必须安装agent以便采集监控数据
- 新加入的节点需要配置agent,手动或者运行脚本

k8s中经常涉及到在node上安装部署应用,它是如何解决上述的问题的呢?答案是DaemonSet。DaemonSet守护进程简称DS,适用于在所有节点或部分节点运行一个daemon守护进程。

DaemonSet 的主要作用,是让你在 k8s 集群里,运行一个 Daemon Pod。

这个 Pod 有如下三个特征:

  1. 这个 Pod 运行在 k8s 集群里的每一个节点(Node)上;

  2. 每个节点上只有一个这样的 Pod 实例;

  3. 当有新的节点加入 Kubernetes 集群后,该 Pod 会自动地在新节点上被创建出来;而当旧节点被删除后,它上面的 Pod 也相应地会被回收掉。

举例:

各种网络插件的 Agent 组件,都必须运行在每一个节点上,用来处理这个节点上的容器网络;

各种存储插件的 Agent 组件,也必须运行在每一个节点上,用来在这个节点上挂载远程存储目录,操作容器的 Volume 目录;

各种监控组件和日志组件,也必须运行在每一个节点上,负责这个节点上的监控信息和日志搜集。

2、DaemonSet 的 API 对象的定义

所有node节点分别下载镜像

# docker pull daocloud.io/daocloud/fluentd-elasticsearch:1.20
# docker pull daocloud.io/daocloud/fluentd-elasticsearch:v2.2.0

fluentd-elasticsearch 镜像功能:

通过 fluentd 将每个node节点上面的Docker 容器里的日志转发到 ElasticSearch 中。

编写daemonset配置文件

Yaml文件内容如下

[root@k8s-master ~]# mkdir set
[root@k8s-master ~]# cd set/
[root@k8s-master set]# vim fluentd-elasticsearch.yaml
apiVersion: apps/v1
kind: DaemonSet  #创建资源的类型
metadata:
 name: fluentd-elasticsearch
 namespace: kube-system
 labels:
  k8s-app: fluentd-logging
spec:
 selector:
  matchLabels:
   name: fluentd-elasticsearch
 template:
  metadata:
   labels:
    name: fluentd-elasticsearch
  spec:
   tolerations:  #容忍污点
   - key: node-role.kubernetes.io/master #污点
     effect: NoSchedule  #描述污点的效应
   containers:
   - name: fluentd-elasticsearch
     image: daocloud.io/daocloud/fluentd-elasticsearch:1.20
     resources:  #限制使用资源
      limits:  #定义使用内存的资源上限
       memory: 300Mi
      requests: #最少需要使用
       cpu: 100m
       memory: 200Mi
     volumeMounts:
     - name: varlog
       mountPath: /var/log
     - name: varlibdockercontainers
       mountPath: /var/lib/docker/containers
       readOnly: true
   volumes:
   - name: varlog
     hostPath:  #定义卷使用宿主机目录
      path: /var/log
   - name: varlibdockercontainers
     hostPath:
      path: /var/lib/docker/containers

单位解释

cpu单位m:代表 “千分之一核心”,比如50m的含义是指50/1000核心,即5%  ---毫核

DaemonSet 没有 replicas 字段

selector :

选择管理所有携带了 name=fluentd-elasticsearch 标签的 Pod。

Pod 的模板用 template 字段定义:

定义了一个使用 fluentd-elasticsearch:1.20 镜像的容器,而且这个容器挂载了两个 hostPath 类型的 Volume,分别对应宿主机的 /var/log 目录和 /var/lib/docker/containers 目录。

fluentd 启动之后,它会从这两个目录里搜集日志信息,并转发给 ElasticSearch 保存。这样,通过 ElasticSearch 就可以很方便地检索这些日志了。Docker 容器里应用的日志,默认会保存在宿主机的 /var/lib/docker/containers/{{. 容器 ID}}/{{. 容器 ID}}-json.log 文件里,这个目录正是 fluentd 的搜集目标。

DaemonSet 如何保证每个 Node 上有且只有一个被管理的 Pod ?

DaemonSet Controller,首先从 Etcd 里获取所有的 Node 列表,然后遍历所有的 Node。这时,它就可以很容易地去检查,当前这个 Node 上是不是有一个携带了 name=fluentd-elasticsearch 标签的 Pod 在运行。

检查结果有三种情况:

1. 没有这种 Pod,那么就意味着要在这个 Node 上创建这样一个 Pod;指定的 Node 上创建新 Pod 用 nodeSelector,选择 Node 的名字即可。
2. 有这种 Pod,但是数量大于 1,那就说明要把多余的 Pod 从这个 Node 上删除掉;删除节点(Node)上多余的 Pod 非常简单,直接调用 Kubernetes API 就可以了。
3. 正好只有一个这种 Pod,那说明这个节点是正常的。

tolerations:

DaemonSet 还会给这个 Pod 自动加上另外一个与调度相关的字段,叫作 tolerations。这个字段意思是这个 Pod,会"容忍"(Toleration)某些 Node 的"污点"(Taint)。

tolerations 字段,格式如下:

apiVersion: v1
kind: Pod
​metadata:
​ name: with-toleration
​spec:
​ tolerations:
​ - key: node.kubernetes.io/unschedulable  #污点的key
​   operator: Exists #将会忽略value;只要有key和effect就行
​   effect: NoSchedule  #污点的作用

含义是:"容忍"所有被标记为 unschedulable"污点"的 Node;"容忍"的效果是允许调度。可以简单地把"污点"理解为一种特殊的 Label。

正常情况下,被标记了 unschedulable"污点"的 Node,是不会有任何 Pod 被调度上去的(effect: NoSchedule)。可是,DaemonSet 自动地给被管理的 Pod 加上了这个特殊的 Toleration,就使得这些 Pod 可以忽略这个限制,保证每个节点上都会被调度一个 Pod。如果这个节点有故障的话,这个 Pod 可能会启动失败,DaemonSet 会始终尝试下去,直到 Pod 启动成功。

DaemonSet 的"过人之处",其实就是依靠 Toleration 实现的

DaemonSet 是一个控制器。在它的控制循环中,只需要遍历所有节点,然后根据节点上是否有被管理 Pod 的情况,来决定是否要创建或者删除一个 Pod。

更多种类的Toleration

可以在 Pod 模板里加上更多种类的 Toleration,从而利用 DaemonSet 实现自己的目的。

比如,在这个 fluentd-elasticsearch DaemonSet 里,给它加上了这样的 Toleration:

tolerations:
- key: node-role.kubernetes.io/master
  effect: NoSchedule

这是因为在默认情况下,Kubernetes 集群不允许用户在 Master 节点部署 Pod。因为,Master 节点默认携带了一个叫作node-role.kubernetes.io/master的"污点"。所以,为了能在 Master 节点上部署 DaemonSet 的 Pod,就必须让这个 Pod"容忍"这个"污点"。

二、DaemonSet实践

  1. 创建 DaemonSet 对象

[root@k8s-master set] # kubectl create -f fluentd-elasticsearch.yaml

DaemonSet 上一般都加上 resources 字段,来限制它的 CPU 和内存使用,防止它占用过多的宿主机资源。

1. 创建成功后,如果有 3 个节点,就会有 3 个 fluentd-elasticsearch Pod 在运行

[root@k8s-master set]# kubectl get pod -n kube-system -l name=fluentd-elasticsearch
NAME                          READY   STATUS    RESTARTS   AGE
fluentd-elasticsearch-6lmnb   1/1     Running   0          21m
fluentd-elasticsearch-9fd7k   1/1     Running   0          21m
fluentd-elasticsearch-vz4n4   1/1     Running   0          21m

2. 查看 DaemonSet 对象

[root@k8s-master set]# kubectl get ds -n kube-system fluentd-elasticsearch
NAME                    DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd-elasticsearch   3         3         3       3            3           <none>          22m

注:k8s 里比较长的 API 对象都有短名字,比如 DaemonSet 对应的是 ds,Deployment 对应的是 deploy。

3. DaemonSet 可以进行版本管理。这个版本,可使用 kubectl rollout history 看到

[root@k8s-master set]# kubectl rollout history daemonset fluentd-elasticsearch -n kube-system
daemonset.apps/fluentd-elasticsearch 
REVISION  CHANGE-CAUSE
1         <none>

4. 把这个 DaemonSet 的容器镜像版本到 v2.2.0

[root@k8s-master set]# kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=daocloud.io/daocloud/fluentd-elasticsearch:v2.2.0 --record -n=kube-system
daemonset.apps/fluentd-elasticsearch image updated

这个 kubectl set image 命令里,第一个 fluentd-elasticsearch 是 DaemonSet 的名字,第二个 fluentd-elasticsearch 是容器的名字。

--record 参数

升级使用到的指令会自动出现在 DaemonSet 的 rollout history 里面,如下所示:

[root@k8s-master set]# kubectl rollout history daemonset fluentd-elasticsearch -n kube-system
daemonset.apps/fluentd-elasticsearch 
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch=daocloud.io/daocloud/fluentd-elasticsearch:v2.2.0 --record=true --namespace=kube-system

有了版本号,也就可以像 Deployment 一样,将 DaemonSet 回滚到某个指定的历史版本了。

部署DASHBOARD应用

注意:最后部署成功之后,因为有5种方式访问dashboard:我们这里只使用Nodport方式访问
1. Nodport方式访问dashboard,service类型改为NodePort
2. loadbalacer方式,service类型改为loadbalacer
3. Ingress方式访问dashboard
4. API server方式访问 dashboard
5. kubectl proxy方式访问dashboard

1.下载yaml文件:

可以自己下载,也可以使用子目录中的内容自己创建
[root@kub-k8s-master ~]# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta8/aio/deploy/recommended.yaml
​
将名称空间修改为默认system
[root@kub-k8s-master ~]# sed -i '/namespace/ s/kubernetes-dashboard/kube-system/g' recommended.yaml

2.下载镜像

由于yaml配置文件中指定的镜像
三台机器都下载
[root@kub-k8s-master ~]# docker pull kubernetesui/dashboard:v2.0.0-beta8
[root@kub-k8s-master ~]# docker pull kubernetesui/metrics-scraper:v1.0.1

3.修改yaml文件

NodePort方式:为了便于本地访问,修改yaml文件,将service改为NodePort 类型:
[root@kub-k8s-master ~]# vim recommended.yaml
...
30 ---
 31 
 32 kind: Service
 33 apiVersion: v1
 34 metadata:
 35   labels:
 36     k8s-app: kubernetes-dashboard
 37   name: kubernetes-dashboard
 38   namespace: kube-system
 39 spec:
 40   type: NodePort   #增加type: NodePort
 41   ports:
 42     - port: 443
 43       targetPort: 8443
 44       nodePort: 31260  #增加nodePort: 31260 注意30000-32767范围
 45   selector:
 46     k8s-app: kubernetes-dashboard
 47 
 48 ---

4.创建应用:

[root@kub-k8s-master ~]# kubectl apply -f recommended.yaml
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created

查看Pod 的状态为running说明dashboard已经部署成功:

[root@kub-k8s-master ~]# kubectl get pod --namespace=kube-system -o wide | grep dashboard
dashboard-metrics-scraper-76585494d8-z7n2h   1/1     Running   0          34s     10.244.1.6       k8s-node2    <none>           <none>
kubernetes-dashboard-594b99b6f4-s9q2h        1/1     Running   0          34s     10.244.2.8       k8s-node1    <none>           <none>

Dashboard 会在 kube-system namespace 中创建自己的 Deployment 和 Service:

[root@kub-k8s-master ~]# kubectl get deployment kubernetes-dashboard --namespace=kube-system
NAME                   READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-dashboard   1/1     1            1           72s
[root@kub-k8s-master ~]# kubectl get service kubernetes-dashboard --namespace=kube-system
NAME                   TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
kubernetes-dashboard   NodePort   10.108.97.179   <none>        443:31260/TCP   49s

5.访问dashboard

官方参考文档:

Deploy and Access the Kubernetes Dashboard | Kubernetes

查看service,TYPE类型已经变为NodePort,端口为31620

[root@kub-k8s-master ~]# kubectl get service -n kube-system | grep dashboard
kubernetes-dashboard   NodePort    10.108.97.179   <none>        443:31260/TCP            101s

查看dashboard运行在哪台机器上面

[root@kub-k8s-master ~]# kubectl get pods -n kube-system -o wide

通过浏览器访问:https://master:31620

因为我的应用运行在master上,又是NodePort方式,所以直接访问master的地址

登录界面如下:

Dashboard 支持 Kubeconfig 和 Token 两种认证方式,这里选择Token认证方式登录:

上面的Token先空着,不要往下点,接下来制作token

创建登录用户

官方参考文档:

Home · kubernetes/dashboard Wiki · GitHub

创建dashboard-adminuser.yaml:

[root@kub-k8s-master ~]# vim dashboard-adminuser.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kube-system

执行yaml文件:

[root@kub-k8s-master ~]# kubectl create -f dashboard-adminuser.yaml
serviceaccount/admin-user created
clusterrolebinding.rbac.authorization.k8s.io/admin-user created

说明:上面创建了一个叫admin-user的服务账号,并放在kube-system命名空间下,并将cluster-admin角色绑定到admin-user账户,这样admin-user账户就有了管理员的权限。默认情况下,kubeadm创建集群时已经创建了cluster-admin角色,直接绑定即可。

查看admin-user账户的token

[root@kub-k8s-master ~]# kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')
Name:         admin-user-token-d2hnw
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: admin-user
              kubernetes.io/service-account.uid: f2a2fb2d-fa04-4535-ac62-2d8779716175
Type:  kubernetes.io/service-account-token
Data
====
ca.crt:     1025 bytes
namespace:  11 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IjBUc19ucm9qbW1zOHJzajhJd2M2bndpWENQSDRrcHRYY3RpWGlMcEhncEUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWQyaG53Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmMmEyZmIyZC1mYTA0LTQ1MzUtYWM2Mi0yZDg3Nzk3MTYxNzUiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06YWRtaW4tdXNlciJ9.f-nhR31D2yMXRvjfM7ZzaKcwOEg_3HYNyxQFioqTO3rKcD6cfLZeOZfZlqrxcrHbcclCsNvIR5ReccKE8GqBJcAcAHZZVSpY9pivtfaU08_VlyQxU4ir3wcCZeeJyAeqEjGhxWJVuouQ-zoofImbaa7wKvSIoEr1jnlOP1rQb51vbekZvDCZue03QBcBRB_ZMfObfLDGI8cuVkYZef9cWFQlI4mEL4kNqHAbmSdJBAVS_6MmF0C1ryIXbe_qM_usm6bsawDsBK8mpuDrXJUU5FBI-rW8qUuZ8QrE_vjRuJkjp5iNCrNd_TyBxWX2jBziMmrWKqofZnGN6ZiqvTAJ8w

把获取到的Token复制到登录界面的Token输入框中:

成功登陆dashboard:

使用Dashboard

Dashboard 界面结构分为三个大的区域:

1.顶部操作区,在这里用户可以搜索集群中的资源、创建资源或退出。
2.左边导航菜单,通过导航菜单可以查看和管理集群中的各种资源。菜单项按照资源的层级分为两类:Cluster 级别的资源 ,Namespace 级别的资源 ,默认显示的是 default Namespace,可以进行切换
3.中间主体区,在导航菜单中点击了某类资源,中间主体区就会显示该资源所有实例,比如点击 Pods。

k8s实现灰度发布

一、常见的发布方式

蓝绿发布:两套环境交替升级,旧版本保留一定时间便于回滚特点:对用户无感,是最安全的发布方式,需要两套系统,对资源要求比较高,成本高

灰度发布:根据比例将老版本升级,例如80%用户访问是老版本,20%用户访问是新版本特点:对自动要求比较高,对比起来系统更加稳定发布,如果遇到问题可以减少影响范围

滚动发布:按批次停止老版本实例,启动新版本实例。特点:节约资源,用户无感,但是部署和回滚的速度慢

金丝雀发布(又称灰度发布、灰度更新)

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

比如10个pod:

更新了一个pod:用的新的代码做的镜像,用于用户访问

9个pod:没有更新

观察金丝雀的健康状况,作为后续发布或回退的依据。 如果金丝测试通过,则把剩余的V1版本全部升级为V2版本。如果金丝雀测试失败,则直接回退金丝雀,发布失败。
优点:灵活,策略自定义,可以按照流量或具体的内容进行灰度出现问题不会影响全网用户
缺点:没有覆盖到所有的用户导致出现问题不好排查

在k8s中实现金丝雀发布

准备两个版本的app镜像
[root@master03 v1]# cat nginx.repo 
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[root@master03 v1]# cat Dockerfile 
FROM centos:7
​
ADD . /etc/yum.repos.d
​
RUN yum install -y nginx && yum clean all && echo v1 > /usr/share/nginx/html/index.html && echo lan >> /usr/share/nginx/html/index.html
​
EXPOSE 80
​
CMD ["nginx","-g","daemon off;"]
[root@master03 v1]# cp /etc/yum.repos.d/CentOS-Base.repo ./ 
[root@master03 v1]# docker build -t testpm/nginx-v1:v1.0 .
​
[root@master03 v2]# cat nginx.repo 
[nginx-stable]                                                              
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[root@master03 v2]# cat Dockerfile 
FROM centos:7
​
ADD . /etc/yum.repos.d
​
RUN yum install -y nginx && yum clean all && echo v2 > /usr/share/nginx/html/index.html && echo lv >> /usr/share/nginx/html/index.html
​
EXPOSE 80
​
CMD ["nginx","-g","daemon off;"]
[root@master03 v2]# cp /etc/yum.repos.d/CentOS-Base.repo ./ 
[root@master03 v2]# docker build -t testpm/nginx-v2:v2.0 .
​
将镜像保存为tar包并传送到node节点(实际工作中直接将镜像上传到harbor仓库)
[root@master03 ~]# docker save -o nginx-v1.tar testpm/nginx-v1:v1.0
[root@master03 ~]# docker save -o nginx-v2.tar testpm/nginx-v2:v2.0
[root@master03 ~]# scp nginx-v* node-1:/root/ 
nginx-v1.tar                              100%  233MB  91.6MB/s   00:02    
nginx-v2.tar                              100%  233MB 105.4MB/s   00:02    
[root@master03 ~]# scp nginx-v* node-2:/root/ 
nginx-v1.tar                              100%  233MB  94.7MB/s   00:02    
nginx-v2.tar                              100%  233MB 103.4MB/s   00:02    
​
[root@node-1 ~]# docker load -i nginx-v1.tar
[root@node-1 ~]# docker load -i nginx-v2.tar
[root@node-2 ~]# docker load -i nginx-v1.tar 
[root@node-2 ~]# docker load -i nginx-v2.tar
创建版本为1的deploy
[root@master01 deploy]# cat app-v1.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
 name: myapp-v1
 namespace: default
spec:
 replicas: 3
 selector:
  matchLabels:
   app: myapp
   version: v1
 template:
  metadata:
   namespace: default
   labels:
     app: myapp
     version: v1
  spec:
    containers:
      - name: myapp
        image: testpm/nginx-v1:v1.0
        imagePullPolicy: IfNotPresent
        ports:
         - containerPort: 80
           
[root@master01 deploy]# kubectl apply -f app-v1.yml
[root@master01 deploy]# kubectl get pod -o wide 
NAME                        READY   STATUS    RESTARTS        AGE     IP            NODE     NOMINATED NODE   READINESS GATES
myapp-v1-654dd88f7f-49vbj   1/1     Running   0               5m31s   10.244.3.18   node-1   <none>           <none>
myapp-v1-654dd88f7f-fvdhm   1/1     Running   0               5m31s   10.244.3.19   node-1   <none>           <none>
myapp-v1-654dd88f7f-r8wdk   1/1     Running   0               10m     10.244.4.6    node-2   <none>           <none>
[root@master01 deploy]# curl 10.244.3.18
v1
2.定义svc用来暴露服务
[root@master01 deploy]# cat app-svc.yml 
apiVersion: v1
kind: Service
metadata:
 name: myapp-svc
 namespace: default
 labels:
  app: myapp
spec:
 type: NodePort
 ports:
  - port: 80
    nodePort: 30070
    targetPort: 80
 selector:
  app: myapp
  version: v1
  
[root@master01 deploy]# kubectl apply -f v1-svc.yml
[root@master01 deploy]# kubectl get svc 
NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes     ClusterIP   10.96.0.1       <none>        443/TCP        12d
myapp-v1-svc   NodePort    10.104.169.32   <none>        80:30070/TCP   2m45s
[root@master01 deploy]# kubectl describe svc myapp-v1-svc
Name:                     myapp-v1-svc
Namespace:                default
Labels:                   app=myapp
Annotations:              <none>
Selector:                 app=myapp,version=v1
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.104.169.32
IPs:                      10.104.169.32
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30070/TCP
Endpoints:                10.244.3.18:80,10.244.3.19:80,10.244.4.6:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
[root@master01 deploy]# kubectl get ep
NAME           ENDPOINTS                                                  AGE
kubernetes     172.16.229.28:6443,172.16.229.29:6443,172.16.229.30:6443   12d
myapp-v1-svc   10.244.3.18:80,10.244.3.19:80,10.244.4.6:80                2m41s
​
[root@master01 ~]# curl http://172.16.229.31:30070
v1
​
[root@master01 deploy]# kubectl get pod -w  #动态查看资源更新过程
NAME                        READY   STATUS    RESTARTS        AGE
myapp-v1-5f75d7f76c-6xdkz   1/1     Running   0               3m10s
myapp-v1-5f75d7f76c-8pgdz   1/1     Running   0               3m13s
myapp-v1-5f75d7f76c-gwp7l   1/1     Running   0               3m11s
在打开一个标签进行灰度发布
[root@master02 ~]# kubectl set image deployment myapp-v1 myapp=testpm/nginx-v2:v2.0 && kubectl rollout pause deployment myapp-v1
deployment.apps/myapp-v1 image updated
deployment.apps/myapp-v1 paused
​
上面的操作把myapp这个容器的镜像更新到testpm/nginx-v2:v2.0版本,更新镜像之后,创建一个新的pod就立即暂停,这就是我们说的灰度发布;如果暂停几个小时之后没有问题,那么取消暂停,就会依次执行后面步骤,把所有pod都升级。
​
[root@master01 deploy]# kubectl get pod -w 
NAME                        READY   STATUS    RESTARTS        AGE
myapp-v1-5f75d7f76c-6xdkz   1/1     Running   0               3m10s
myapp-v1-5f75d7f76c-8pgdz   1/1     Running   0               3m13s
myapp-v1-5f75d7f76c-gwp7l   1/1     Running   0               3m11s
myapp-v1-558cd9cb57-zqkkb   0/1     Pending   0               0s
myapp-v1-558cd9cb57-zqkkb   0/1     Pending   0               1s
myapp-v1-558cd9cb57-zqkkb   0/1     ContainerCreating   0               1s
myapp-v1-558cd9cb57-zqkkb   1/1     Running             0               32s
​
[root@master01 deploy]# kubectl get pod 
NAME                        READY   STATUS    RESTARTS        AGE
myapp-v1-558cd9cb57-zqkkb   1/1     Running   0               95s #刚刚刚创建的pod
myapp-v1-5f75d7f76c-6xdkz   1/1     Running   0               5m30s
myapp-v1-5f75d7f76c-8pgdz   1/1     Running   0               5m33s
myapp-v1-5f75d7f76c-gwp7l   1/1     Running   0               5m31s
pod-1                       1/1     Running   1 (3d18h ago)   3d19h
[root@master01 deploy]# kubectl describe svc myapp-v1-svc
Name:                     myapp-v1-svc
Namespace:                default
Labels:                   app=myapp
Annotations:              <none>
Selector:                 app=myapp,version=v1
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.104.169.32
IPs:                      10.104.169.32
Port:                     <unset>  80/TCP
TargetPort:               80/TCP
NodePort:                 <unset>  30070/TCP
Endpoints:                10.244.3.20:80,10.244.3.21:80,10.244.3.22:80 + 1 more...
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>
[root@master01 deploy]# kubectl get rs 
NAME                  DESIRED   CURRENT   READY   AGE
myapp-v1-558cd9cb57   1         1         1       5m39s
myapp-v1-5f75d7f76c   3         3         3       9m37s
​
测试访问
[root@master01 deploy]# curl 172.16.229.32:30070
v2
[root@master01 deploy]# curl 172.16.229.32:30070
v1
[root@master01 deploy]# curl 172.16.229.32:30070
v1
[root@master01 deploy]# curl 172.16.229.32:30070
v1
[root@master01 deploy]# curl 172.16.229.32:30070
v2
​
通过运行一段时间发现新版本的pod并没有出现问题,那么现在进行全部更新,将暂停取消
[root@master02 ~]# kubectl rollout resume deployment myapp-v1
deployment.apps/myapp-v1 resumed
​
[root@master01 deploy]# kubectl get pod
NAME                        READY   STATUS    RESTARTS        AGE
myapp-v1-7f855f5856-62484   1/1     Running   0               42s
myapp-v1-7f855f5856-7txdt   1/1     Running   0               43s
myapp-v1-7f855f5856-kn9gt   1/1     Running   0               4m55s
pod-1                       1/1     Running   1 (3d19h ago)   3d20h
[root@master01 deploy]# kubectl get rs
NAME                  DESIRED   CURRENT   READY   AGE
myapp-v1-697bc45f78   0         0         0       15m
myapp-v1-7f855f5856   3         3         3       4m59s
​
在此进行测试访问
[root@master01 deploy]# curl 172.16.229.32:30070
v2
[root@master01 deploy]# curl 172.16.229.32:30070
v2
[root@master01 deploy]# curl 172.16.229.32:30070
v2

遇到的经典问题

完美解决k8s master节点无法ping node节点中的IP或Service NodePort的IP

问题描述1

按教程默认安装的calico,启动后发现跨节点pod互相ping不通。

查看并修改calico node的daemonset配置:(vm环境)

kubectl edit DaemonSet calico-node -n calico-system


发现: IP_AUTODETECTION_METHOD 这个环境变量的值是first_found,我的机器网上eth0并不是第一个,这样可能有问题。因此修改成 value: "interface=eth0" 并保存退出,居然没生效。

原因是:

该方法命令虽然可以执行,但是我这里ds么没有生效。k8s 1.22,通过operator生成的calico,不是calico.yaml,如果是calico.yaml,直接修改文件中的参数即可。

因此要用另一种方法来修改:

我的custom-resources.yaml 如下,加上红色部分:

然后重新执行:

 kubectl apply -f custom-resources.yaml

然后再:

kubectl set env ds/calico-node -n calico-system IP_AUTODETECTION_METHOD=interface=eth0

就可以了。(注意eth0只是举例,具体看自己的机器网卡)

问题描述2

使用搭建好了K8S集群,先是node节点加入k8s集群时,用的内网IP,导致master节点无法操作node节点中的pod(这里的不能操作,指定是无法查看node节点中pod的日志、启动描述、无法进入pod内部,即 kubectl logs 、kubectl describe、kubectl exec -it 等等的命令都不能)

解决办法:解决公网下,k8s calico master节点无法访问node节点创建的pod

问题描述3

master节点能正常访问node节点创建的pod,即问题一所产生的问题

但是master节点无法ping通node节点创建pod所属的Service的IP

进入主题:我创建了一个mynginx的Service,type为NodePort,如下:

但是master节点去ping 10.96.177.91失败,node节点ping是成功的

解决方法:

2.1、需要使用 ipvs 替换iptables

  • 所有节点都需要操作,我这里只有一台master和一台node,所以我只在master操作

vim /etc/sysctl.d/k8s.conf

加入:net.ipv4.ip_forward = 1

  • 更新配置

sysctl --system

2.2、安装IPVS

yum -y install ipvsadm  ipset
  • 临时生效

modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack_ipv4
  • 永久生效

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

2.3、配置kube-proxy

  • 在master上操作,因使用kubeadmin安装,所以操作方式如下

kubectl edit cm kube-proxy -n kube-system
  • 找到mode的位置,把mode修改为ipvs

2.4、重启kube-proxy

  • 在master重启kube-proxy

kubectl  get pod -n kube-system | grep kube-proxy | awk '{print $1}' | xargs kubectl delete pod -n kube-system
  • 使用:kubectl get pod -A 发现kube-proxy成功运行了2个新的pod

2.5、验证kube-proxy的pod是否开启ipvs

补充下,k8s有几个节点,就会有几个kube-proxy-xxxx的pod,我们需要去查看对应节点的 kube-proxy-xxxx,不要去查看master节点的 kube-proxy-xxxx

kubectl logs kube-proxy-7cb69 -n kube-system
  • kube-proxy-7cb69:这个pod名称,根据node节点实际情况填写

开启失败!原因:

can't set sysctl net/ipv4/vs/conn_reuse_mode, kernel version must be at least 4.1

搜索了下,是linux内核版本太低,K8S要求kernel必须大于等于4.1版本

解决方案

CentOS 7.9升级内核(kernel-5.4.254),解决1 proxier.go:389] can‘t set sysctl net/ipv4/vs/conn_reuse_mode,

2.6、验证master节点是否能ping通NodePort方式Service的IP

成功解决!

  • 访问Servie

 

<think>首先,用户的问题是:“kubernetes 种探针”,这翻译成英文是“Kubernetes three types of probes”。我需要用中文回答这个问题。 问题要求:回答完问题后,必须根据用户的问题及我的回答提出3-5个相关问题。相关问题必须输出在回答的末尾,第一行必须是“§§相关问题§§”,并且禁止换行分散。 示例格式显示:回答后,直接输出“§§相关问题§§”,然后列出相关问题,每个问题以数字开头。 所以,我的回答结构应该是: 1. 回答用户的问题。 2. 在末尾输出“§§相关问题§§”。 3. 然后列出3-5个相关问题,每个问题以数字开头(如1. 2. 3.)。 现在,内容:Kubernetes种探针。Kubernetes种健康检查探针: - Liveness Probe(存活探针) - Readiness Probe(就绪探针) - Startup Probe(启动探针) 我需要用中文解释这些: - 存活探针(Liveness Probe):用于检测容器是否还在运行。如果失败,Kubernetes会重启容器。 - 就绪探针(Readiness Probe):用于检测容器是否准备好接收流量。如果失败,Kubernetes会停止将流量路由到该容器。 - 启动探针(Startup Probe):用于检测容器是否已经启动完成。在启动期间,Kubernetes会延迟其他探针的执行。 确保回答简洁明了。 然后,基于用户的问题和我的回答,提出3-5个相关问题。相关问题应该与Kubernetes探针相关,帮助用户深入学习。 可能的相关问题: 1. 存活探针和就绪探针有什么区别? 2. 如何配置Kubernetes中的启动探针? 3. 探针失败会导致什么后果? 4. Kubernetes探针支持哪些类型的检查? 5. 为什么需要启动探针? 确保有3-5个。 最后,输出时: - 回答部分。 - 然后“§§相关问题§§”在单独一行。 - 接着相关问题列表,每个问题占一行,以数字开头。 禁止换行分散,意思是相关问题应该紧凑输出,不要有多余的空行。 完整输出: [回答内容] §§相关问题§§ 1. 问题1 2. 问题2 3. 问题3 现在,撰写回答。</think>Kubernetes 中的种探针是用于容器健康检查的机制,帮助确保应用在集群中稳定运行。它们分别是: 1. **存活探针(Liveness Probe)**:检测容器是否仍在正常运行。如果失败,Kubernetes 会重启容器以恢复服务。例如,配置 HTTP 请求检查应用是否响应。 2. **就绪探针(Readiness Probe)**:检测容器是否准备好接收流量。如果失败,Kubernetes 会从服务端点移除该容器,避免将请求路由给它。常用于启动依赖或初始化阶段。 3. **启动探针(Startup Probe)**:检测容器是否已成功启动。在启动期间,它会延迟其他探针的执行,防止过早重启。适用于启动较慢的应用,如数据库初始化。 这些探针可通过 YAML 文件配置,支持 HTTP、TCP 或命令执行等检查方式,提升应用的高可用性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值