更新时间:2023年3月
文章目录
Headless Services
有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP
)的值为 "None"
来创建 Headless Service
对于 Headless Services ,kubernetes 不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了 selectors(选择器)
带有 selectors
对于带有 selectors
的 Headless Services,,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象, 并且修改 DNS 配置返回 A 或 AAAA 记录(IPv4 或 IPv6 地址),通过这个地址直接到达 Service
的后端 Pod 上
不带 selectors
对没有定义选择算符的无头服务,控制平面不会创建 EndpointSlice 对象。 然而 DNS 系统会查找和配置以下之一:
- 对于
type: ExternalName
的服务,查找和配置其 CNAME 记录 - 对所有其他类型的服务,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS 的 A / AAAA 记录
- 对于 IPv4 端点,DNS 系统创建 A 记录
- 对于 IPv6 端点,DNS 系统创建 AAAA 记录
StatefulSet
参考-2:有状态的应用 | Kubernetes
StatefulSet 是用来管理有状态应用的工作负载 API 对象
和 Deployment 不同的是, StatefulSet 为它创建的每个 Pod 维护了一个永久 ID(身份表示)。虽然这些 Pod 是基于相同的规则和约定来创建的, 但不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID
应用场景
StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:
- 稳定的、唯一的网络标识符
- 稳定的、持久的存储
- 有序的、优雅的部署和扩缩
- 有序的、自动的滚动更新
常见使用场景例如:MySQL 的主从搭建
特点
Pod 标识
StatefulSet 创建的 Pod 都具有唯一的标识,该标识包括顺序序号、稳定的网络 ID 和稳定的存储。 该标识和 Pod 是一直绑定的,与该 Pod 调度到哪个节点上无关
唯一的顺序序号
StatefulSet 中的每个 Pod 都会分配一个整数序号, 该序号在这个 StatefulSet 上是唯一的。假设有 N 个 Pod,默认情况下,这些 Pod 将被从 0 到 N-1 的序号
固定网络 ID
- StatefulSet 中的每个 Pod 的主机名是固定的,格式为
$( StatefulSet 名称 )-$( 序号 )
- StatefulSet 可以使用 Headless Services 来管理 Pod 的网络域。这个服务的格式为:
$( 服务名称 ).$( 命名空间 ).svc.$(CLUSTER_DNS_DOMAIN)
- 每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为:
$( pod 名称 ).$( 所属服务的 DNS 域名 )
示例
集群域名 | 服务(名字空间/名字) | StatefulSet(命名空间/名字) | StatefulSet 域名 | Pod DNS | Pod 主机名 |
---|---|---|---|---|---|
cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0…N-1}.nginx.default.svc.cluster.local | web-{0…N-1} |
cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0…N-1}.nginx.foo.svc.cluster.local | web-{0…N-1} |
kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0…N-1}.nginx.foo.svc.kube.local | web-{0…N-1} |
固定存储
当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts
会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。 请注意,当 Pod 或者 StatefulSet 被删除时,PersistentVolumeClaims不会被删除,因此与 PersistentVolumeClaims 相关联的 PersistentVolume 也不会被删除。要删除它必须通过手动方式来完成。
pod 标签
当 StatefulSet 创建 Pod 时, 它会添加一个标签 statefulset.kubernetes.io/pod-name
,该标签值设置为 Pod 名称。 这个标签允许你给 StatefulSet 中的特定 Pod 绑定一个 Service。常用场景:MySQL 读写分离
部署及扩缩容保证
- 对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为
0..N-1
- 当删除 Pod 时,它们是逆序终止的,顺序为
N-1..0
- 在将扩容缩容操作应用到 Pod 之前,在它前面的所有 Pod 必须是 Running 和 Ready 状态
- 在一个 Pod 终止之前,在它之后的所有 Pod 必须完全关闭。
Pod 管理策略
StatefulSet 允许放宽其排序保证, 同时通过它的 .spec.podManagementPolicy
域保持其唯一性和身份保证
OrderedReady
,(默认设置)Parallel
, StatefulSet 控制器并行的启动或终止所有的 Pod, 启动或者终止其他 Pod 前,无需等待 Pod 进入 Running 和 Ready 或者完全停止状态。 这个选项只会影响扩缩操作的行为,更新则不会被影响
限制
- 给定的存储必须是持久性存储(PV),可以提前制备,也可以通过
storage class
来制备 - 删除或者扩缩 StatefulSet 并不会删除它关联的存储卷。kubernetes 1.23 之后,可以通过
.spec.persistentVolumeClaimRetentionPolicy
配置是否删除PVC(alpha 版本),参考:StatefulSet - PersistentVolumeClaim 保留 | Kubernetes - 当前版本的 kubernetes,StatefulSet 需要 Headless Services 来负责 Pod 的网络标识
- 当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序且正常地终止,可以在删除之前将 StatefulSet 缩容到 0
- 在默认的 Pod 管理策略(
OrderedReady
)下,使用滚动更新时,如果进入无法运行或就绪的状态,需要手动干预,详见:StatefulSet - 强制回滚 | Kubernetes
示例- StatefulSet 部署 Zookeeper
参考 - 1:运行一个有状态的应用程序 | Kubernetes
部署
创建 Service
创建两个 Service,包含 3 个端口,分别为客户端访问端口 2181、服务之间数据同步端口 2888、服务选举端口 3888
$ vim svc-zookeeper-ensemble.yaml
# Headless Service
apiVersion: v1
kind: Service
metadata:
name: zk-hs
labels:
app: zk
spec:
ports:
- port: 2888
name: server
- port: 3888
name: leader-election
clusterIP: None
selector:
app: zk
---
# 正常 Service,type 为默认的 ClusterIP
apiVersion: v1
kind: Service
metadata:
name: zk-cs
labels:
app: zk
spec:
ports:
- port: 2181
name: client
selector:
app: zk
应用 Service 的声明文件
$ kubectl apply -f svc-zookeeper-ensemble.yaml
创建 PDB(可选)
创建 PodDisruptionBudget,用于限制最大不可用 Pod 数为 1,超过 1 时服务即宕机
$ vim zk-pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
selector:
matchLabels:
app: zk
maxUnavailable: 1
应用 Service 的声明文件
$ kubectl apply -f zk-pdb.yaml
创建 StorageClass
# 请先提前安装配置好 NFS 服务和 k8s 的 NFS 制备器
$ vim sc-zookeeper-ensemble.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
reclaimPolicy: Retain # PV 的删除策略
mountOptions:
- soft
- nfsvers=4.2
- noatime # 访问文件时不更新文件 inode 中的时间戳,高并发环境可提高性能
parameters:
archiveOnDelete: "true" # 删除 pod 时保留 pod 数据,默认为 false,不保留数据
应用 StorageClass 的声明文件
$ kubectl apply -f ./sc-zookeeper-ensemble.yaml
创建 ConfigMap
$ vim zk-configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: zk-configmap
data:
zoo_servers: "server.1=zk-0.zk-hs:2888:3888;2181 server.2=zk-1.zk-hs:2888:3888;2181 server.3=zk-2.zk-hs:2888:3888;2181"
# zoo_data_dir: "/zk/data"
# zoo_conf_dir: "/zk/conf"
应用 ConfigMap 的声明文件
$ kubectl apply -f ./zk-configmap.yaml
创建 StatefulSet
注:可以配置的变量参考:zookeeper - Official Image | Docker Hub
该 StatefulSet 不支持动态扩容缩容,如果有扩容缩容需求可以用 zkCli.sh
的 reconfig
命令,并配合 SERVER_JVMFLAGS
变量和 addauth
实现鉴权
$ vim zk-statefulset.yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zk
spec:
selector:
matchLabels:
app: zk
serviceName: zk-hs
replicas: 3
template:
metadata:
labels:
app: zk
spec:
terminationGracePeriodSeconds: 10
containers:
- name: zookeeper
image: "zookeeper:3.7.1-temurin"
imagePullPolicy: IfNotPresent
# 就绪探针和存活探针
readinessProbe:
httpGet:
path: /commands/ruok
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 5
periodSeconds: 3
livenessProbe:
httpGet:
path: /commands/ruok
port: 8080
initialDelaySeconds: 30
timeoutSeconds: 5
periodSeconds: 3
#
ports:
- containerPort: 2181
name: client
- containerPort: 2888
name: peer
- containerPort: 3888
name: leader-election
# 设置环境变量 ZOO_SERVERS,用于配置集群节点和端口信息
env:
- name: ZOO_SERVERS
valueFrom:
configMapKeyRef:
name: zk-configmap
key: zoo_servers
# 若配置了 ZOO_DATA_DIR 变量,则必须重写 docker-entrypoint.sh
# 或使用 command 覆盖 ENTRYPOINT 指令
#- name: ZOO_DATA_DIR
# valueFrom:
# configMapKeyRef:
# name: zk-configmap
# key: zoo_data_dir
# 若配置了 ZOO_CONF_DIR 变量,则一些其他变量不会自动配置,需自行配置
# 详见 docker-entrypoint.sh
#- name: ZOO_CONF_DIR
# valueFrom:
# configMapKeyRef:
# name: zk-configmap
# key: zoo_conf_dir
#
volumeMounts:
- name: datadir
mountPath: /data
# mountPath: $(ZOO_DATA_DIR)
# 使用 args 替换官方 Dockerfile 的 CMD 指令,添加一些 server.${id} 的配置操作
# 也可以使用 command 替换官方 Dockerfile 的 ENTRYPOINT 指令
args:
- bash
- "-c"
- |
# 基于 Pod 序号生成 Zookeeper 服务的 ID
## 根据主机名获取 Pod 序号
[[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
## 添加偏移量,使 ID 符合 zookeeper 要求的 1~254 范围
ZOO_MY_ID=$(( 1 + ${ordinal} ))
## 写入 ID
echo "${ZOO_MY_ID:-1}" > "$ZOO_DATA_DIR/myid"
# 需要替换本机的 server.id 对应的 IP 地址为全部监听
sed -i "s/server\.$ZOO_MY_ID\=[a-z0-9.-]*/server.$ZOO_MY_ID=0.0.0.0/" "$ZOO_CONF_DIR/zoo.cfg"
# 最后加上官方 Dockerfile 原本的 CMD 启动参数
zkServer.sh start-foreground
volumeClaimTemplates:
- metadata:
name: datadir
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: nfs-client
resources:
requests:
storage: 0.5Gi
---
应用声明文件
$ kubectl apply -f ./zk-statefulset.yaml
检查
检查 Pod 运行情况
$ kubectl get pods -o wide
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
zk-0 1/1 Running 0 8m8s 172.20.195.44 192.168.111.187 <none> <none>
zk-1 1/1 Running 0 7m54s 172.20.119.216 192.168.111.188 <none> <none>
zk-2 1/1 Running 0 7m40s 172.20.195.45 192.168.111.187 <none> <none>
......
检查角色情况
# pod zk-0
$ kubectl exec -it zk-0 -- zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower
# pod zk-1
$ kubectl exec -it zk-1 -- zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: leader
# pod zk-2
$ kubectl exec -it zk-2 -- zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower
检查集群运行情况
选择一台有 Zookeeper 环境的宿主机,通过 Pod IP 连接 pod
$ zkCli.sh -server 172.20.195.44
# 查询配置信息,初次查询会比较慢
[zk: 172.20.195.44(CONNECTING) 0] config
......
WatchedEvent state:SyncConnected type:None path:null
server.1=0.0.0.0:2888:3888:participant;0.0.0.0:2181
server.2=zk-1.zk-hs:2888:3888:participant;0.0.0.0:2181
server.3=zk-2.zk-hs:2888:3888:participant;0.0.0.0:2181
version=0
# 测试写入数据
[zk: 172.20.195.44(CONNECTED) 7] create /test-str "hello cluster"
Created /test-str
# 另一台 zk 上查询数据
$ echo "get /test-str" | zkCli.sh -server 172.20.119.216
......
WatchedEvent state:SyncConnected type:None path:null
hello cluster
......
DaemonSet
DaemonSet 可以确保全部(或者选择一部分)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod
应用场景
- 在每个节点上运行集群守护进程
- 在每个节点上运行日志收集守护进程
- 在每个节点上运行监控守护进程
选择节点
- 如果指定了
.spec.template.spec.nodeSelector
,DaemonSet 控制器将在能够与 Node 选择算符 匹配的节点上创建 Pod - 也可以指定
.spec.template.spec.affinity
,之后 DaemonSet 控制器将在能够与 节点亲和性 匹配的节点上创建 Pod - 如果没有指定以上字段,则 DaemonSet 控制器将会在所有节点上创建 Pod
污点和容忍度
可以在 DaemonSet 的 Pod 模板中定义自己的容忍度并将其添加到 DaemonSet Pod。DaemonSet 控制器会自动将一组容忍度添加到 DaemonSet Pod
容忍度键名 | 效果 | 描述 |
---|---|---|
node.kubernetes.io/not-ready | NoExecute | DaemonSet Pod 可以被调度到不健康或还不准备接受 Pod 的节点上。在这些节点上运行的所有 DaemonSet Pod 将不会被驱逐。 |
node.kubernetes.io/unreachable | NoExecute | DaemonSet Pod 可以被调度到从节点控制器不可达的节点上。在这些节点上运行的所有 DaemonSet Pod 将不会被驱逐。 |
node.kubernetes.io/disk-pressure | NoSchedule | DaemonSet Pod 可以被调度到具有磁盘压力问题的节点上。 |
node.kubernetes.io/memory-pressure | NoSchedule | DaemonSet Pod 可以被调度到具有内存压力问题的节点上。 |
node.kubernetes.io/pid-pressure | NoSchedule | DaemonSet Pod 可以被调度到具有进程压力问题的节点上。 |
node.kubernetes.io/unschedulable | NoSchedule | DaemonSet Pod 可以被调度到不可调度的节点上。 |
node.kubernetes.io/network-unavailable | NoSchedule | 仅针对请求主机联网的 DaemonSet Pod 添加此容忍度,即 Pod 具有 spec.hostNetwork: true 。这些 DaemonSet Pod 可以被调度到网络不可用的节点上。 |
示例-使用 DaemonSet 部署 node-exporter
参考-1:Monitoring Linux host metrics with the Node Exporter | Prometheus
要点
- 使用 DaemonSet,确保每个 kubernetes 节点都部署了 node-exporter
- 要将宿主机的
/proc
和/sys
等目录挂载至容器 - 使用宿主机网络、进程号等资源
部署 node-exporter
$ vim daemonset-prom-node-exporter.yaml
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app.kubernetes.io/component: exporter
app.kubernetes.io/name: node-exporter
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app.kubernetes.io/component: exporter
app.kubernetes.io/name: node-exporter
template:
metadata:
labels:
app.kubernetes.io/component: exporter
app.kubernetes.io/name: node-exporter
spec:
hostNetwork: true
hostPID: true
hostIPC: true
containers:
- name: prometheus-node-exporter
image: prom/node-exporter:v1.4.1
imagePullPolicy: IfNotPresent
ports:
- name: metrics
containerPort: 9100
hostPort: 9100
protocol: TCP
resources:
limits:
cpu: 250m
memory: 180Mi
requests:
cpu: 100m
memory: 50Mi
volumeMounts:
- mountPath: /host/sys
mountPropagation: HostToContainer
name: sys
readOnly: true
- mountPath: /host/proc
mountPropagation: HostToContainer
name: proc
readOnly: true
- mountPath: /host/root
mountPropagation: HostToContainer
name: root
readOnly: true
args:
- "--no-collector.wifi"
- "--no-collector.hwmon"
- "--collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+|var/lib/kubelet/pods/.+)($|/)"
- "--collector.netclass.ignored-devices=^(veth.*)$"
- "--path.sysfs=/host/sys"
- "--path.procfs=/host/proc"
- "--path.rootfs=/host/root"
volumes:
- hostPath:
path: /sys
name: sys
- hostPath:
path: /proc
name: proc
- hostPath:
path: /
name: root
---
应用声明
$ kubectl apply -f daemonset-prom-node-exporter.yaml
Prometheus 配置
# prometheus 新增 job
- job_name: 'kubernetes-node'
kubernetes_sd_configs:
- role: node
relabel_configs:
- source_labels: [__address__]
regex: '(.*):10250'
replacement: '${1}:9100'
target_label: __address__
action: replace
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
注:10250 是 kubelet 的 API 端口
检查
检查是否正常访问 metrics
$ curl http://192.168.111.185:9100/metrics -I
HTTP/1.1 200 OK
Content-Type: text/plain; version=0.0.4; charset=utf-8
Date: Mon, 20 Mar 2023 19:03:55 GMT
登录 prometheus 的 web 页面查看是否正常采集数据