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 中用于管理有状态应用的工作负载控制器。与 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-0
、my-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-0
、web-1
、web-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 支持两种更新策略:RollingUpdate
和 OnDelete
。
- 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 有如下三个特征:
这个 Pod 运行在 k8s 集群里的每一个节点(Node)上;
每个节点上只有一个这样的 Pod 实例;
当有新的节点加入 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实践
-
创建 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