k8s应用综合实例

k8s应用综合实例

目录

k8s应用综合实例

目录

原文链接

推荐文章

实验环境

实验软件

本节实战

预期

原理

高可用

稳定性

避免单点故障

使用 PDB

健康检查

服务质量 QoS

QoS类型

资源回收策略

滚动更新

失败原因

零宕机

HPA

安全性

持久化

Ingress

FAQ

session问题

关于我

最后

原文链接

k8s应用综合实例 | 彦

推荐文章

我的开源项目:

开源项目 | 彦

实验环境

k8s集群环境:

k8s v1.28.7(sealos部署的1master,2work节点)

containerd v1.7.13

cilium v1.14.7 (cni)

节点内核版本:5.4.270-1.el7.elrepo.x86_64

应用环境:

wordpress:6.4.3-apache

mysql:5.7

实验软件

链接:百度网盘 请输入提取码

提取码:mlpe

2024.3.9-k8s应用综合实例

本节实战

实战名称

配置Pod反向亲和性、PDB、健康检查、QoS、preStop` 生命周期钩子、hpa、secret、持久化(nfs、pvc)、ingress。

预期

本节我们将用一个 Wordpress 示例来尽可能将 Kubernetes 的基础知识串联起来,我们需要达到的目的是让 Wordpress 应用具有高可用、滚动更新的过程中不能中断服务、数据要持久化不能丢失、当应用负载太高的时候能够自动进行扩容、当然还有 HTTPS 访问等等,这些是我们的应用部署到线上环境基本上要具备的一些能力,接下来我们就来一步一步完成这些需求。

原理

首先要部署 Wordpress 应用,我们肯定需要知道 Wordpress 是如何运行起来的,Wordpress 是一个基于 PHP 和 MySQL 的流行的开源内容管理系统,拥有丰富的插件和模板系统。到这里我们应该就清楚应该如何去运行 Wordpress 了,一个能够解析 PHP 的程序,和 MySQL 数据库就可以了。

我们要想在 Kubernetes 系统中来运行,肯定需要使用到 Docker 镜像了,对于 Wordpress 应用程序本身官方提供了镜像 https://hub.docker.com/_/wordpress,也给出了说明如何运行,可以通过一系列环境变量去指定 MySQL 数据库的配置,只需要将这些参数配置上直接运行即可。

我们知道 Wordpress 应用本身会频繁的和 MySQL 数据库进行交互,这种情况下如果将二者用容器部署在同一个 Pod 下面是不是要高效很多,因为一个 Pod 下面的所有容器是共享同一个 network namespace 的。

尝试:将php和mysql服务放在一1个pod里

下面我们就来部署我们的应用,将我们的应用都部署到 kube-example 这个命名空间下面。

  • 所以首先创建一个命名空间:(namespace.yaml)

apiVersion: v1

kind: Namespace

metadata:

name: kube-example

本次使用命令行:

[root@master ~]#kubectl create ns kube-example

namespace/kube-example created

[root@master ~]#kubectl get ns kube-example

NAME STATUS AGE

kube-example Active 31s

[root@master ~]#

  • 然后编写部署到 Kubernetes 下面的资源清单:

# deployment.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: wordpress

namespace: kube-example

labels:

app: wordpress

spec:

selector:

matchLabels:

app: wordpress

template:

metadata:

labels:

app: wordpress

spec:

containers:

- name: wordpress

image: wordpress:6.1.1-apache

ports:

- containerPort: 80

name: wdport

env:

- name: WORDPRESS_DB_HOST

value: localhost:3306

- name: WORDPRESS_DB_USER

value: wordpress

- name: WORDPRESS_DB_PASSWORD

value: wordpress

- name: mysql

image: mysql:5.7

imagePullPolicy: IfNotPresent

args: # 新版本镜像有更新,需要使用下面的认证插件环境变量配置才会生效!

- --default_authentication_plugin=mysql_native_password

- --character-set-server=utf8mb4

- --collation-server=utf8mb4_unicode_ci

ports:

- containerPort: 3306

name: dbport

env:

- name: MYSQL_ROOT_PASSWORD

value: rootPassW0rd

- name: MYSQL_DATABASE

value: wordpress

- name: MYSQL_USER

value: wordpress

- name: MYSQL_PASSWORD

value: wordpress

由于我们这里 MySQL 和 Wordpress 在同一个 Pod 下面,所以在 Wordpress 中我们指定数据库地址的时候是用的 localhost:3306,因为这两个容器已经共享同一个 network namespace 了,这点很重要。

  • 然后如果我们要想把这个服务暴露给外部用户还得创建一个 Service 或者 Ingress 对象,这里我们一步一步来,暂时先创建一个 NodePort 类型的 Service:

# service.yaml

apiVersion: v1

kind: Service

metadata:

name: wordpress

namespace: kube-example

spec:

selector:

app: wordpress

type: NodePort

ports:

- name: web

port: 80

targetPort: wdport

因为只需要暴露 Wordpress 这个应用,所以只匹配了一个名为 wdport 的端口。

  • 现在我们来创建上面的几个资源对象:

#$ kubectl apply -f namespace.yaml

$ kubectl apply -f deployment.yaml

$ kubectl apply -f service.yaml

  • 接下来就是等待拉取镜像,启动 Pod:

[root@master ~]#kubectl get pods -n kube-example

NAME READY STATUS RESTARTS AGE

wordpress-7dc9f8748b-sb44t 2/2 Running 0 118m

[root@master ~]# kubectl get svc -n kube-example

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

wordpress NodePort 10.96.2.145 <none> 80:31409/TCP 119m

[root@master ~]#

  • 测试

当 Pod 启动完成后,我们就可以通过上面的 http://<任意节点IP>:31230 这个 NodePort 端口来访问应用了。

我们仔细想一想这一种方式有什么问题?

首先一个 Pod 中的所有容器并没有启动的先后顺序,所以很有可能当 wordpress 这个容器启动起来去连接 mysql 这个容器的时候,mysql 还没有启动起来;

另外一个问题是现在我们的应用是不是只有一个副本?会有单点问题,应用的性能也是一个问题,由于 Wordpress 应用本身是无状态应用,所以这种情况下一般我们只需要多部署几个副本即可,比如这里我们在 Deployment 的 YAML 文件中加上 replicas:3 这个属性,这个时候有一个什么问题呢?

由于 MySQL 是有状态应用,每一个 Pod 里面的数据库的数据都是独立的,他们并没有共享,也就是说这 3 个 Pod 相当于是独立的 3 个 Wordpress 实例,所以应该怎么办呢?

拆分,把 Wordpress 和 MySQL 这两个容器部署成独立的 Pod 就可以了,这样我们只需要对 Wordpress 应用增加副本,而数据库 MySQL 还是一个实例,所有的应用都连接到这一个数据库上面,是不是就可以解决这个问题了。

最终方案:将php和mysql服务放在2个pod里

高可用

现在我们将 Pod 中的两个容器进行拆分,将 Wordpress 和 MySQL 分别部署,然后 Wordpress 用多个副本进行部署就可以实现应用的高可用了。由于 MySQL 是有状态应用,一般来说需要用 StatefulSet 来进行管理,但是我们这里部署的 MySQL 并不是集群模式,而是单副本的,所以用 Deployment 也是没有问题的,当然如果要真正用于生产环境还是需要集群模式的

  • 测试之前,先删除刚才创建的资源

[root@master ~]#kubectl delete -f deployment.yaml

deployment.apps "wordpress" deleted

[root@master ~]#kubectl delete -f service.yaml

service "wordpress" deleted

[root@master ~]#

  • 配置资源

# mysql.yaml

apiVersion: v1

kind: Service

metadata:

name: mysql

namespace: kube-example

labels:

app: mysql

spec:

ports:

- port: 3306

targetPort: dbport

selector:

app: mysql

---

apiVersion: apps/v1

kind: StatefulSet

metadata:

name: mysql

namespace: kube-example

labels:

app: mysql

spec:

selector:

matchLabels:

app: mysql

template:

metadata:

labels:

app: mysql

spec:

containers:

- name: mysql

image: mysql:5.7

imagePullPolicy: IfNotPresent

args: # 新版本镜像有更新,需要使用下面的认证插件环境变量配置才会生效

- --default_authentication_plugin=mysql_native_password

- --character-set-server=utf8mb4

- --collation-server=utf8mb4_unicode_ci

ports:

- containerPort: 3306

name: dbport

env:

- name: MYSQL_ROOT_PASSWORD

value: rootPassW0rd

- name: MYSQL_DATABASE

value: wordpress

- name: MYSQL_USER

value: wordpress

- name: MYSQL_PASSWORD

value: wordpress

我们这里给 MySQL 应用添加了一个 Service 对象,是因为 Wordpress 应用需要来连接数据库,之前在同一个 Pod 中用 localhost 即可,现在需要通过 Service 的 DNS 形式的域名进行连接。

  • 直接创建上面资源对象:

$ kubectl apply -f mysql.yaml

service/mysql created

statefulset.apps/mysql created

[root@master ~]#kubectl get sts,pod,svc -nkube-example

NAME READY AGE

statefulset.apps/mysql 1/1 36s

NAME READY STATUS RESTARTS AGE

pod/mysql-0 1/1 Running 0 36s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

service/mysql ClusterIP 10.96.2.26 <none> 3306/TCP 36s

[root@master ~]#

  • 接下来创建独立的 Wordpress 服务,对应的资源对象如下:

# wordpress.yaml

apiVersion: v1

kind: Service

metadata:

name: wordpress

namespace: kube-example

labels:

app: wordpress

spec:

selector:

app: wordpress

tier: frontend

type: NodePort

ports:

- name: web

port: 80

targetPort: wdport

---

apiVersion: apps/v1

kind: Deployment

metadata:

name: wordpress

namespace: kube-example

labels:

app: wordpress

tier: frontend

spec:

replicas: 2

selector:

matchLabels:

app: wordpress

tier: frontend

template:

metadata:

labels:

app: wordpress

tier: frontend

spec:

containers:

- name: wordpress

image: wordpress:6.4.3-apache

ports:

- containerPort: 80

name: wdport

env:

- name: WORDPRESS_DB_HOST

value: mysql:3306

- name: WORDPRESS_DB_USER

value: wordpress

- name: WORDPRESS_DB_PASSWORD

value: wordpress

注意这里的环境变量 WORDPRESS_DB_HOST 的值将之前的 localhost 地址更改成了上面 MySQL 服务的 DNS 地址,完整的域名应该是 mysql.kube-example.svc.cluster.local:3306,由于这两个应该都处于同一个命名空间,所以直接简写成 mysql:3306 也是可以的。

  • 创建上面资源对象:

$ kubectl apply -f wordpress.yaml

service/wordpress created

deployment.apps/wordpress created

[root@master ~]#kubectl get pods -l app=wordpress -n kube-example

NAME READY STATUS RESTARTS AGE

mysql-0 1/1 Running 0 2m17s

wordpress-8449b9b9d6-4pl67 1/1 Running 0 55s

wordpress-8449b9b9d6-kp4p7 1/1 Running 0 55s

[root@master ~]#

可以看到都已经是 Running 状态了,然后我们需要怎么来验证呢?

  • 是不是我们能想到的就是去访问下我们的 Wordpress 服务就可以了,我们这里还是使用的一个 NodePort 类型的 Service 来暴露服务:

[root@master ~]#kubectl get svc -l app=wordpress -n kube-example

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

mysql ClusterIP 10.96.2.26 <none> 3306/TCP 2m45s

wordpress NodePort 10.96.1.213 <none> 80:31753/TCP 83s

[root@master ~]#

可以看到 wordpress 服务产生了一个 32411 的端口,现在我们就可以通过 http://<任意节点的NodeIP>:32411 访问我们的应用了,在浏览器中打开,如果看到 wordpress 跳转到了安装页面,证明我们的部署是正确的,如果没有出现预期的效果,那么就需要去查看下 Pod 的日志来排查问题了。

  • 根据页面提示,填上对应的信息,点击“安装”即可,最终安装成功后,我们就可以看到熟悉的首页界面了:

稳定性

现在 Wodpress 应用已经部署成功了,那么就万事大吉了吗?如果我们的网站访问量突然变大了怎么办,如果我们要更新我们的镜像该怎么办?所以要保证我们的网站能够非常稳定的提供服务,我们做得还不够,我们可以通过做些什么事情来提高网站的稳定性呢?

避免单点故障

为什么会有单点故障的问题呢?我们不是部署了多个副本的 Wordpress 应用吗?当我们设置 replicas=1 的时候肯定会存在单点故障问题,如果大于 1 但是所有副本都调度到了同一个节点的是不是同样就会存在单点问题了,这个节点挂了所有副本就都挂了,所以我们不仅需要设置多个副本数量,还需要让这些副本调度到不同的节点上,来打散避免单点故障。这个利用 Pod 反亲和性来实现了。

  • 我们可以加上如下所示的配置:

[root@master ~]#vim wordpress.yaml

……

# 软策略,本次后面测试就用软策略了哈。

affinity:

podAntiAffinity:

preferredDuringSchedulingIgnoredDuringExecution: # 软策略

- weight: 1

podAffinityTerm:

topologyKey: kubernetes.io/hostname

labelSelector:

matchExpressions:

- key: app

operator: In

values:

- wordpress

……

# 硬策略

affinity:

podAntiAffinity:

requiredDuringSchedulingIgnoredDuringExecution: # 硬策略

- labelSelector:

matchExpressions:

- key: app

operator: In

values:

- wordpress

topologyKey: kubernetes.io/hostname

这里的意思就是如果一个节点上面有 app=wordpress 这样的 Pod 的话,那么我们的 Pod 就尽可能别调度到这个节点上面来,因为我们这里的节点并不多,所以我使用的是软策略,因为如果使用硬策略的话,如果应用副本数超过了节点数就必然会有 Pod 调度不成功。如果你线上节点非常多的话(节点数大于 Pod 副本数),建议使用硬策略,更新后我们可以查看下 3 个副本被分散在了不同的节点上。

[root@master ~]#kubectl get po -nkube-example -owide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES

mysql-0 1/1 Running 0 46m 10.0.2.138 node1 <none> <none>

wordpress-788cbc7dff-njbm8 1/1 Running 0 8s 10.0.1.106 node2 <none> <none>

wordpress-788cbc7dff-pcrv5 1/1 Running 0 8s 10.0.2.141 node1 <none> <none>

wordpress-788cbc7dff-tnqhn 1/1 Running 0 8s 10.0.1.41 node2 <none> <none>

[root@master ~]#

🚩 特别注意:

后续的实验,因为这里配置了反亲和性,所以需要先删除资源,再重新创建资源的哈。

使用 PDB

有些时候线上的某些节点需要做一些维护操作,比如要升级内核,这个时候我们就需要将要维护的节点进行驱逐操作,驱逐节点首先是将节点设置为不可调度,这样可以避免有新的 Pod 调度上来,然后将该节点上的 Pod 全部删除,ReplicaSet 控制器检测到 Pod 数量减少了就会重新创建一个新的 Pod,调度到其他节点上面的,这个过程是先删除,再创建,并非是滚动更新,因此更新过程中,如果一个服务的所有副本都在被驱逐的节点上,则可能导致该服务不可用。

如果服务本身存在单点故障,所有副本都在同一个节点,驱逐的时候肯定就会造成服务不可用了,这种情况我们使用上面的反亲和性和多副本就可以解决这个问题。

但是如果我们的服务本身就被打散在多个节点上,这些节点如果都被同时驱逐的话,那么这个服务的所有实例都会被同时删除,这个时候也会造成服务不可用了,这种情况下我们可以通过配置 PDB(PodDisruptionBudget)对象来避免所有副本同时被删除。比如我们可以设置在驱逐的时候 wordpress 应用最多只有一个副本不可用,其实就相当于逐个删除并在其它节点上重建:

  • 创建资源清单

# pdb.yaml

apiVersion: policy/v1

kind: PodDisruptionBudget

metadata:

name: wordpress-pdb

namespace: kube-example

spec:

maxUnavailable: 1

selector:

matchLabels:

app: wordpress

tier: frontend

  • 直接创建这个资源对象即可:

$ kubectl apply -f pdb.yaml

poddisruptionbudget.policy/wordpress-pdb created

$ kubectl get pdb -n kube-example

NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE

wordpress-pdb N/A 1 1 9s

关于 PDB 的更多详细信息可以查看官方文档:Specifying a Disruption Budget for your Application | Kubernetes。

健康检查

我们的应用现在还有一个非常重要的功能没有提供,那就是健康检查,我们知道健康检查是提高应用健壮性非常重要的手段,当我们检测到应用不健康的时候我们希望可以自动重启容器,当应用还没有准备好的时候我们也希望暂时不要对外提供服务,所以我们需要添加我们前面经常提到的 liveness probe 和 rediness probe 两个健康检测探针。

检查探针的方式有很多,我们这里当然可以认为如果容器的 80 端口可以成功访问那么就是健康的,对于一般的应用提供一个健康检查的 URL 会更好。这里我们添加一个如下所示的可读性探针,为什么不添加存活性探针呢?这里其实是考虑到线上错误排查的一个问题,如果当我们的应用出现了问题,然后就自动重启去掩盖错误的话,可能这个错误就会被永远忽略掉了,所以其实这是一个折衷的做法。不使用存活性探针,而是结合监控报警,保留错误现场,方便错误排查,但是可读写探针是一定需要添加的

#wordpress.yaml

……

readinessProbe:

tcpSocket:

port: 80

initialDelaySeconds: 5

periodSeconds: 5

增加上面的探针,每 5s 检测一次应用是否可读,这样只有当 readinessProbe 探针检测成功后才表示准备好接收流量了,这个时候才会更新 Service 的 Endpoints 对象。

服务质量 QoS

QoS 是 Quality of Service 的缩写,即服务质量。为了实现资源被有效调度和分配的同时提高资源利用率,Kubernetes 针对不同服务质量的预期,通过 QoS 来对 Pod 进行服务质量管理。对于一个 Pod 来说,服务质量体现在两个具体的指标:CPU 和内存。当节点上内存资源紧张时,Kubernetes 会根据预先设置的不同 QoS 类别进行相应处理。

QoS 主要分为 Guaranteed、Burstable 和 Best-Effort三类,优先级从高到低。我们先分别来介绍下这三种服务类型的定义。

QoS类型

1、Guaranteed(有保证的)

属于该级别的 Pod 有以下两种:

  • Pod 中的所有容器都且设置了 CPU 和内存的 limits
  • Pod 中的所有容器都设置了 CPU 和内存的 requests 和 limits ,且单个容器内的requests==limits(requests 不等于 0)

Pod 中的所有容器都且仅设置了 limits,如下所示:

containers:

- name: foo

resources:

limits:

cpu: 10m

memory: 1Gi

- name: bar

resources:

limits:

cpu: 100m

memory: 100Mi

Pod 中的所有容器都设置了 requests 和 limits,且单个容器内的 requests==limits 的情况:

containers:

- name: foo

resources:

limits:

cpu: 10m

memory: 1Gi

requests:

cpu: 10m

memory: 1Gi

- name: bar

resources:

limits:

cpu: 100m

memory: 100Mi

requests:

cpu: 100m

memory: 100Mi

容器 foo 和 bar 内 resources 的 requests 和 limits 均相等,该 Pod 的 QoS 级别属于 Guaranteed。

2、Burstable(不稳定的)

Pod 中只要有一个容器的 requests 和 limits 的设置不相同,那么该 Pod 的 QoS 即为 Burstable。如下所示容器 foo 指定了 resource,而容器 bar 未指定:

containers:

- name: foo

resources:

limits:

cpu: 10m

memory: 1Gi

requests:

cpu: 10m

memory: 1Gi

- name: bar

容器 foo 设置了内存 limits,而容器 bar 设置了 CPU limits:

containers:

- name: foo

resources:

limits:

memory: 1Gi

- name: bar

resources:

limits:

cpu: 100m

需要注意的是如果容器指定了 requests 而未指定 limits,则 limits 的值等于节点资源的最大值,如果容器指定了 limits 而未指定 requests,则 requests 的值等于 limits。

3、Best-Effort(尽最大努力)

如果 Pod 中所有容器的 resources 均未设置 requests 与 limits,该 Pod 的 QoS 即为 Best-Effort。如下所示容器 foo 和容器 bar 均未设置 requests 和 limits:

containers:

- name: foo

resources:

- name: bar

resources:

资源回收策略

Kubernetes 通过 CGroup 给 Pod 设置 QoS 级别,当资源不足时会优先 kill 掉优先级低的 Pod,在实际使用过程中,通过 OOM 分数值来实现,OOM 分数值范围为 0-1000,OOM 分数值根据 OOM_ADJ参数计算得出。

对于 Guaranteed 级别的 Pod,OOM_ADJ 参数设置成了-998,对于 Best-Effort 级别的 Pod,OOM_ADJ 参数设置成了1000,对于 Burstable 级别的 Pod,OOM_ADJ 参数取值从 2 到 999。

QoS Pods 被 kill 掉的场景和顺序如下所示:

  • Best-Effort Pods:系统用完了全部内存时,该类型 Pods 会最先被 kill 掉
  • Burstable Pods:系统用完了全部内存,且没有 Best-Effort 类型的容器可以被 kill 时,该类型的 Pods 会被 kill 掉
  • Guaranteed Pods:系统用完了全部内存,且没有 Burstable 与 Best-Effort 类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉

所以如果资源充足,可将 QoS Pods 类型设置为 Guaranteed,用计算资源换业务性能和稳定性,减少排查问题时间和成本。如果想更好的提高资源利用率,业务服务可以设置为 Guaranteed,而其他服务根据重要程度可分别设置为 Burstable 或 Best-Effort,这就要看具体的场景了。

比如我们这里如果想要尽可能提高 Wordpress 应用的稳定性,我们可以将其设置为 Guaranteed 类型的 Pod,我们现在没有设置 resources 资源,所以现在是 Best-Effort 类型的 Pod。

现在如果要想给应用设置资源大小,就又有一个问题了,应该如何设置合适的资源大小呢?其实这就需要我们对自己的应用非常了解才行了,一般情况下我们可以先不设置资源,然后可以根据我们的应用的并发和访问量来进行压力测试,基本上可以大概计算出应用的资源使用量。

🚩 Fortio压测工具安装文档

Fortio | 彦

我们这里使用 Apache Bench(AB Test) 或者 Fortio(Istio 测试工具) 这样的测试工具来测试,我们这里使用 Fortio 这个测试工具,比如每秒 1000 个请求和 8 个并发的连接的测试命令如下所示:

$ fortio load -a -c 5 -qps 1000 -t 60s "http://k8s.youdianzhishi.com:32411"

16:42:13 I scli.go:88> Starting Φορτίο 1.53.1 h1:aeAWrnkXIBEdhueQLRwvaM5INulybDP+ZgOChgKz/NQ= go1.19.1 arm64 darwin

Fortio 1.53.1 running at 1000 queries per second, 10->10 procs, for 1m0s: http://k8s.youdianzhishi.com:32411

16:42:13 I httprunner.go:99> Starting http test for http://k8s.youdianzhishi.com:32411 with 5 threads at 1000.0 qps and parallel warmup

Starting at 1000 qps with 5 thread(s) [gomax 10] for 1m0s : 12000 calls each (total 60000)

# ......

Sockets used: 19 (for perfect keepalive, would be 5)

Uniform: false, Jitter: false, Catchup allowed: true

IP addresses distribution:

43.137.1.207:32411: 19

Code -1 : 14 (8.4 %)

Code 200 : 153 (91.6 %)

Response Header Sizes : count 167 avg 290.86228 +/- 45.56 min 0 max 298 sum 48574

Response Body/Total Sizes : count 167 avg 50216.443 +/- 9456 min 0 max 52409 sum 8386146

All done 167 calls (plus 5 warmup) 1837.452 ms avg, 2.7 qps

Successfully wrote 6213 bytes of Json data to 2023-03-14-164213_k8s_youdianzhishi_com_32411_bogon.json

也可以通过浏览器(执行 fortio server 命令)查看到最终测试结果:

在测试期间我们可以用如下所示的命令查看应用的资源使用情况:

$ kubectl top pods -l app=wordpress -n kube-example

NAME CPU(cores) MEMORY(bytes)

mysql-0 15m 213Mi

wordpress-55b57b745f-f28tr 88m 95Mi

wordpress-55b57b745f-gn8jc 89m 90Mi

wordpress-55b57b745f-r7x2w 90m 94Mi

我们可以看到内存基本上都是处于 100Mi 以内,CPU 消耗也很小,但是由于 CPU 是可压缩资源,也就是说超过了限制应用也不会挂掉的,只是会变慢而已。所以我们这里可以给 Wordpress 应用添加如下所示的资源配置,如果你集群资源足够的话可以适当多分配一些资源:

resources:

limits:

cpu: 150m

memory: 150Mi

requests:

cpu: 150m

memory: 150Mi

配置后更新应用。

滚动更新

Deployment 控制器默认的就是滚动更新的更新策略,该策略可以在任何时间点更新应用的时候保证某些实例依然可以正常运行来防止应用 down 掉,当新部署的 Pod 启动并可以处理流量之后,才会去杀掉旧的 Pod。在使用过程中我们还可以指定 Kubernetes 在更新期间如何处理多个副本的切换方式,比如我们有一个 3 副本的应用,在更新的过程中是否应该立即创建这 3 个新的 Pod 并等待他们全部启动,或者杀掉一个之外的所有旧的 Pod,或者还是要一个一个的 Pod 进行替换?

如果我们从旧版本到新版本进行滚动更新,只是简单的通过输出显示来判断哪些 Pod 是存活并准备就绪的,那么这个滚动更新的行为看上去肯定就是有效的,但是往往实际情况就是从旧版本到新版本的切换的过程并不总是十分顺畅的,应用程序很有可能会丢弃掉某些客户端的请求。比如我们在 Wordpress 应用中添加上如下的滚动更新策略,随便更改以下 Pod Template 中的参数,比如容器名更改为 blog:

strategy:

type: RollingUpdate

rollingUpdate:

maxSurge: 1

maxUnavailable: 0

然后更新应用,同时用 Fortio 工具在滚动更新过程中来测试应用是否可用:

自己这里没亲自做测试哦!🤣

$ kubectl apply -f wordpress.yaml

$ fortio load -a -c 5 -qps 500 -t 60s "http://wordpress.onedayxyy.com:31122"

# ......

Sockets used: 33 (for perfect keepalive, would be 5)

Uniform: false, Jitter: false, Catchup allowed: true

IP addresses distribution:

43.137.1.207:32411: 33

Code -1 : 25 (14.0 %)

Code 200 : 153 (86.0 %)

Response Header Sizes : count 178 avg 295.18539 +/- 31.62 min 0 max 317 sum 52543

Response Body/Total Sizes : count 178 avg 48525.348 +/- 1.101e+04 min 0 max 52428 sum 8637512

All done 178 calls (plus 5 warmup) 1710.081 ms avg, 2.9 qps

Successfully wrote 7184 bytes of Json data to 2023-03-14-165414_k8s_youdianzhishi_com_32411_bogon.json

从上面的输出可以看出有部分请求处理失败了(502),要弄清楚失败的原因就需要弄明白当应用在滚动更新期间重新路由流量时,从旧的 Pod 实例到新的实例究竟会发生什么,首先让我们先看看 Kubernetes 是如何管理工作负载连接的。

失败原因

我们这里通过 NodePort 去访问应用,实际上也是通过每个节点上面的 kube-proxy 通过更新 iptables 规则来实现的。

Kubernetes 会根据 Pods 的状态去更新 Endpoints 对象,这样就可以保证 Endpoints 中包含的都是准备好处理请求的 Pod。一旦新的 Pod 处于活动状态并准备就绪后,Kubernetes 就将会停止旧的 Pod,从而将 Pod 的状态更新为 “Terminating”,然后从 Endpoints 对象中移除,并且发送一个 SIGTERM 信号给 Pod 的主进程。SIGTERM 信号就会让容器以正常的方式关闭,并且不接受任何新的连接。Pod 从 Endpoints 对象中被移除后,前面的负载均衡器就会将流量路由到其他(新的)Pod 中去。因为在负载均衡器注意到变更并更新其配置之前,终止信号就会去停用 Pod,而这个重新配置过程又是异步发生的,并不能保证正确的顺序,所以就可能导致很少的请求会被路由到已经终止的 Pod 上去了,也就出现了上面我们说的情况。

零宕机

那么如何增强我们的应用程序以实现真正的零宕机迁移更新呢?

首先,要实现这个目标的先决条件是我们的容器要正确处理终止信号,在 SIGTERM 信号上实现优雅关闭。下一步需要添加 readiness 可读探针,来检查我们的应用程序是否已经准备好来处理流量了。为了解决 Pod 停止的时候不会阻塞并等到负载均衡器重新配置的问题,我们还需要使用 preStop 这个生命周期的钩子,在容器终止之前调用该钩子。

生命周期钩子函数是同步的,所以必须在将最终停止信号发送到容器之前完成,在我们的示例中,我们使用该钩子简单的等待,然后 SIGTERM 信号将停止应用程序进程。同时,Kubernetes 将从 Endpoints 对象中删除该 Pod,所以该 Pod 将会从我们的负载均衡器中排除,基本上来说我们的生命周期钩子函数等待的时间可以确保在应用程序停止之前重新配置负载均衡器:

readinessProbe:

# ...

lifecycle:

preStop:

exec:

command: ["/bin/bash", "-c", "sleep 20"]

我们这里使用 preStop 设置了一个 20s 的宽限期,Pod 在真正销毁前会先 sleep 等待 20s,这就相当于留了时间给 Endpoints 控制器和 kube-proxy 更新去 Endpoints 对象和转发规则,这段时间 Pod 虽然处于 Terminating 状态,即便在转发规则更新完全之前有请求被转发到这个 Terminating 的 Pod,依然可以被正常处理,因为它还在 sleep,没有被真正销毁。

现在,当我们去查看滚动更新期间的 Pod 行为时,我们将看到正在终止的 Pod 处于 Terminating 状态,但是在等待时间结束之前不会关闭的,如果我们使用 Fortio 重新测试下,则会看到零失败请求的理想状态。

HPA

现在应用是固定的 3 个副本,但是往往在生产环境流量是不可控的,很有可能一次活动就会有大量的流量,3 个副本很有可能抗不住大量的用户请求,这个时候我们就希望能够自动对 Pod 进行伸缩,直接使用前面我们学习的 HPA 这个资源对象就可以满足我们的需求了。

本次,因本地没fortio工具,这里不做演示。

  • 直接使用kubectl autoscale命令来创建一个 HPA 对象

$ kubectl autoscale deployment wordpress --namespace kube-example --cpu-percent=80 --min=3 --max=8

horizontalpodautoscaler.autoscaling/hpa-demo autoscaled

$ kubectl get hpa -n kube-example

NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE

wordpress Deployment/wordpress <unknown>/80% 3 8 0 4s

此命令创建了一个关联资源 wordpress 的 HPA,最小的 Pod 副本数为 3,最大为 8。HPA 会根据设定的 cpu 使用率(80%)动态的增加或者减少 Pod 数量。

  • 同样,使用上面的 Fortio 工具来进行压测一次,看下能否进行自动的扩缩容:

$ fortio load -a -c 8 -qps 1000 -t 60s "http://k8s.youdianzhishi.com:32411"

$ fortio load -a -c 8 -qps 1000 -t 60s "http://wordpress.onedayxyy.com:30310"

  • 在压测的过程中我们可以看到 HPA 的状态变化以及 Pod 数量也变成了 6 个:

$ kubectl get hpa -n kube-example

NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE

wordpress Deployment/wordpress 98%/20% 3 6 6 2m40s

$ kubectl get pods -n kube-example

NAME READY STATUS RESTARTS AGE

wordpress-79d756cbc8-f6kfm 1/1 Running 0 21m

wordpress-79d756cbc8-kspch 1/1 Running 0 32s

wordpress-79d756cbc8-sf5rm 1/1 Running 0 32s

wordpress-79d756cbc8-tsjmf 1/1 Running 0 20m

wordpress-79d756cbc8-v9p7n 1/1 Running 0 32s

wordpress-79d756cbc8-z4wpp 1/1 Running 0 21m

wordpress-mysql-5756ccc8b-zqstp 1/1 Running 0 3d19h

当压测停止以后正常 5 分钟后就会自动进行缩容,变成最小的 3 个 Pod 副本。

安全性

安全性这个和具体的业务应用有关系,比如我们这里的 Wordpress 也就是数据库的密码属于比较私密的信息。

  • 我们可以使用 Kubernetes 中的 Secret 资源对象来存储比较私密的信息:

$ kubectl create secret generic wordpress-db-pwd --from-literal=dbpwd=wordpress -n kube-example

secret/wordpress-db-pwd created

  • 然后将 Deployment 资源对象中的数据库密码环境变量通过 Secret 对象读取:

env:

- name: WORDPRESS_DB_HOST

value: wordpress-mysql:3306

- name: WORDPRESS_DB_USER

value: wordpress

- name: WORDPRESS_DB_PASSWORD

valueFrom:

secretKeyRef:

name: wordpress-db-pwd

key: dbpwd

这样我们就不会在 YAML 文件中看到明文的数据库密码了,当然安全性都是相对的,Secret 资源对象也只是简单的将密码做了一次 Base64 编码而已,对于一些特殊场景安全性要求非常高的应用,就需要使用其他功能更加强大的密码系统来进行管理了,比如 Vault。

持久化

现在还有一个比较大的问题就是我们的数据还没有做持久化,MySQL 数据库没有做,Wordpress 应用本身也没有做,这显然不是一个合格的线上应用。

🚩 nfs共享存储部署文档

1、NFS存储 | 彦

  • 这里我们就直接使用之前的 nfs 共享存储来实现:

$ kubectl get sc

NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE

nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 40m

但是由于 Wordpress 应用是多个副本,需要同时在多个节点进行读写,也就是 accessModes 需要 ReadWriteMany 模式,一般来说块存储是不支持的,需要文件系统。

  • 我们这里的 NFS 是支持的,创建一个如下所示的 PVC:

# storage.yaml

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

name: wordpress-pvc

namespace: kube-example

labels:

app: wordpress

spec:

storageClassName: nfs-client

accessModes:

- ReadWriteMany

resources:

requests:

storage: 2Gi

部署:

[root@master ~]#kubectl apply -f storage.yaml

persistentvolumeclaim/wordpress-pvc created

  • 然后在 Wordpress 应用上添加对 /var/www/html 目录的挂载声明:

volumeMounts:

- name: wordpress-data

mountPath: /var/www/html

volumes:

- name: wordpress-data

persistentVolumeClaim:

claimName: wordpress-pvc

  • 然后在 MySQL 应用上添加对 /var/lib/mysql 目录的挂载声明:

volumeMounts:

- name: mysql-data

mountPath: /var/lib/mysql

volumeClaimTemplates:

- metadata:

name: mysql-data

spec:

accessModes: ["ReadWriteOnce"]

storageClassName: nfs-client

resources:

requests:

storage: 1Gi

  • 重新更新就可以正常启动了。

  • 验证

[root@master ~]#kubectl get all -nkube-example -owide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES

pod/mysql-0 1/1 Running 0 108m 10.0.2.157 node1 <none> <none>

pod/wordpress-7c955bb55-jrsb7 1/1 Running 0 107m 10.0.2.184 node1 <none> <none>

pod/wordpress-7c955bb55-m9k4v 1/1 Running 0 107m 10.0.1.129 node2 <none> <none>

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR

service/mysql ClusterIP 10.96.3.163 <none> 3306/TCP 108m app=mysql

service/wordpress NodePort 10.96.2.155 <none> 80:30349/TCP 107m app=wordpress

NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR

deployment.apps/wordpress 2/2 2 2 107m wordpress wordpress:6.4.3-apache app=wordpress

NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR

replicaset.apps/wordpress-7c955bb55 2 2 2 107m wordpress wordpress:6.4.3-apache app=wordpress,pod-template-hash=7c955bb55

NAME READY AGE CONTAINERS IMAGES

statefulset.apps/mysql 1/1 108m mysql mysql:5.7

NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE

horizontalpodautoscaler.autoscaling/wordpress Deployment/wordpress 0%/80% 2 8 2 3h9m

[root@master ~]#

[root@master ~]#kubectl get pvc -A

NAMESPACE NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE

kube-example mysql-data-mysql-0 Bound pvc-f9a8084a-a1d0-407d-8270-9b93023b0889 1Gi RWO nfs-client 114m

kube-example wordpress-pvc Bound pvc-f4665c14-6711-4ff7-8725-3841f5e7bac1 2Gi RWX nfs-client 112m

[root@master ~]#

Ingress

对于一个线上的应用对外暴露服务用一个域名显然是更加合适的,上面我们使用的 NodePort 类型的服务不适合用于线上生产环境。

🚩 ingress-nginx部署文档

v3-实战-yaml方式安装ingress-nginx-2023.3.11(测试成功)(DaemonSet方式) | 彦

  • 我们通过 Ingress 对象来暴露服务(需要安装一个 Ingress 控制器):

# ingress.yaml

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: wordpress

namespace: kube-example

spec:

ingressClassName: nginx

rules:

- host: wordpress.onedayxyy.com

http:

paths:

- path: /

pathType: Prefix

backend:

service:

name: wordpress

port:

number: 80

  • 直接创建上面的资源对象即可:

$ kubectl apply -f ingress.yaml

ingress.networking.k8s.io/wordpress created

[root@master ~]#kubectl get ingress -nkube-example

NAME CLASS HOSTS ADDRESS PORTS AGE

wordpress nginx wordpress.onedayxyy.com 80 6m40s

[root@master ~]#kubectl get svc -nkube-example

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

mysql ClusterIP 10.96.2.175 <none> 3306/TCP 11m

wordpress NodePort 10.96.2.136 <none> 80:32274/TCP 11m

[root@master ~]#kubectl get svc -ningress-nginx

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

ingress-nginx-controller LoadBalancer 10.96.0.217 <pending> 80:31122/TCP,443:32606/TCP 8m20s

ingress-nginx-controller-admission ClusterIP 10.96.2.235 <none> 443/TCP 8m20s

[root@master ~]#kubectl get po -ningress-nginx -owide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES

ingress-nginx-admission-create-txs54 0/1 Completed 0 8m28s 10.0.2.40 node1 <none> <none>

ingress-nginx-admission-patch-rd8s5 0/1 Completed 0 8m28s 10.0.1.113 node2 <none> <none>

ingress-nginx-controller-765fp 1/1 Running 0 8m28s 10.0.2.211 node1 <none> <none>

ingress-nginx-controller-p9x56 1/1 Running 0 8m28s 10.0.0.183 master <none> <none>

ingress-nginx-controller-qqdx4 1/1 Running 0 8m28s 10.0.1.95 node2 <none> <none>

[root@master ~]#

  • 然后对域名 wordpress.youdianzhishi.com 加上对应的 DNS 解析即可正常访问了,这样即使我们的数据库或者 Wordpress 应用挂掉了也不会丢失数据了。

172.29.9.80 wordpress.onedayxyy.com

172.29.9.81 wordpress.onedayxyy.com

172.29.9.82 wordpress.onedayxyy.com

在自己pc里做一个域名解析就好。

在浏览器里访问:

注意:这里的ingrexx-nginx默认是一个 LoadBalancer 类型的 Service,我们知道该类型是用于云服务商的,我们这里在本地环境,暂时不能使用,但是可以通过他的 NodePort 来对外暴露。

到这里就完成了我们一个生产级别应用的部署,虽然应用本身很简单,但是如果真的要部署到生产环境我们需要关注的内容就比较多了,当然对于线上应用除了上面我们提到的还有很多值得我们关注的地方,比如监控报警、日志收集等方面都是我们关注的层面,这些在后面的课程中我们再慢慢去了解。

FAQ

session问题

这里还有个遗留session问题,待后续处理。

关于我

我的博客主旨:

  • 排版美观,语言精炼;
  • 文档即手册,步骤明细,拒绝埋坑,提供源码;
  • 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!

🍀 微信二维码

x2675263825 (舍得), qq:2675263825。

🍀 微信公众号

《云原生架构师实战》

🍀 个人博客站点

明心静性 | 彦

🍀 语雀

One · 语雀

🍀 csdn

一念一生~one-CSDN博客

🍀 知乎

一个人 - 知乎

最后

好了,关于本次就到这里了,感谢大家阅读,最后祝大家生活快乐,每天都过的有意义哦,我们下期见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/526483.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

区块链和人工智能的关系以及经典案例

目录 1.区块链与人工智能的关系 2.应用案例&#xff1a;基于区块链的医疗数据共享平台 2.1背景 2.2方案 2.3优势 2.4挑战 区块链技术和人工智能&#xff08;AI&#xff09;是两种不同的技术&#xff0c;但它们之间存在着互补关系。区块链技术提供了一种安全、透明、去中心…

《JAVA与模式》之桥梁模式

系列文章目录 文章目录 系列文章目录前言一、桥梁模式的用意二、桥梁模式的结构三、使用场景四、不使用模式的解决方案五、实现发送加急消息前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂…

前端vite+vue3——可视化页面性能耗时指标(fmp、fp)

文章目录 ⭐前言&#x1f496;vue3系列文章 ⭐可视化fmp、fp指标&#x1f496; MutationObserver 计算 dom的变化&#x1f496; 使用条形图展示 fmp、fp时间 ⭐项目代码⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于 前端vitevue3——可视化页面性能耗时…

【解读】OWASP 大语言模型(LLM)安全测评基准V1.0

大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;是指参数量巨大、能够处理海量数据的模型, 此类模型通常具有大规模的参数&#xff0c;使得它们能够处理更复杂的问题&#xff0c;并学习更广泛的知识。自2022 年以来&#xff0c;LLM技术在得到了广泛的应…

企业AI转型之路:策略与实践

目录 前言1 试点项目&#xff1a;积累AI经验1.1 选择有实际价值的项目1.2 创新氛围的激发1.3 员工对新技术的接受度提升 2 建立高效的内部AI团队2.1 团队独立性与高层直报2.2 初期资金支持与资源整合 3 提供全面的AI培训计划3.1 针对不同层次的培训3.2 多样化培训形式3.3 内部人…

软考71-上午题-【面向对象技术2-UML】-UML中的图2

一、用例图 上午题&#xff0c;考的少&#xff1b;下午题&#xff0c;考的多。 1-1、用例图的定义 用例图展现了一组用例、参与者以及它们之间的关系。 用例图用于对系统的静态用例图进行建模。 可以用下列两种方式来使用用例图&#xff1a; 1、对系统的语境建模&#xff1b…

常见3大web漏洞

常见3大web漏洞 XSS攻击 描述&#xff1a; 跨站脚本&#xff08;cross site script&#xff09;-简称XSS&#xff0c;常出现在web应用中的计算机安全漏桶、web应用中的主流攻击方式。 攻击原理&#xff1a; 攻击者利用网站未对用户提交数据进行转义处理或者过滤不足的缺点。 …

【蓝桥杯-单片机】LED和按键小练习:Led彩灯控制系统

文章目录 【蓝桥杯-单片机】LED和按键小练习&#xff1a;Led彩灯控制系统01 题目描述02 题目解答03 本题总结整体逻辑框架&#xff08;详细版&#xff09;整体逻辑框架&#xff08;缩略版&#xff09;按键读取模块按键消抖模块流水灯显示模式&#xff08;1&#xff09;从上向下…

NoSQL--3.MongoDB配置(Linux版)

目录 2.2 Linux环境下操作 2.2.1 传输MongoDB压缩包到虚拟机&#xff1a; 2.2.2 启动MongoDB服务&#xff1a; 2.2 Linux环境下操作 2.2.1 传输MongoDB压缩包到虚拟机&#xff1a; &#xff08;笔者使用XShell传输&#xff09; 如果不想放在如图的路径&#xff0c;删除操作…

前端框架的发展历史介绍

前端框架的发展历史是Web技术进步的一个重要方面。从最初的简单HTML页面到现在的复杂单页应用程序&#xff08;SPA&#xff09;&#xff0c;前端框架和库的发展极大地推动了Web应用程序的构建方式。以下是一些关键的前端框架和库&#xff0c;以及它们的发布年份、创建者和主要特…

seo蜘蛛池的概念!蚂蚁SEO

蜘蛛池是一种特殊的网络营销技术&#xff0c;它的主要作用是吸引搜索引擎爬虫&#xff0c;提高网站的收录和排名&#xff0c;从而增加网站的流量和曝光度。 蚂蚁SEO是一个SEO工具&#xff0c;可以帮助您提高网站权重&#xff0c;吸引更多的搜索引擎爬虫&#xff0c;提高网站的…

排序算法的对比

类别排序方法时间复杂度空间复杂度稳定性平均情况特殊情况 插入 排序 插入排序基本有序最优稳定希尔排序不稳定 选择 排序 选择排序不稳定堆排序不稳定 交换 排序 冒泡排序稳定快速排序基本有序最差不稳定归并排序稳定基数排序稳定