也可查看语雀文档地址
https://www.yuque.com/u41500387/gu9vnh/lc29douap823zq34?singleDoc# 《K8S创建一个自动扩展的计算器 API 服务》
一、主要任务描述
创建一个自动扩展的计算器 API 服务:
要求:
(一)开发一个简单的计算器 API 服务
使用 Python 的 http.server 模块。这个服务应该提供以下端点:
/add?a=<num1>&b=<num2>:接受 GET 请求,返回两个数的和。
/subtract?a=<num1>&b=<num2>:接受 GET 请求,返回两个数的差。
/multiply?a=<num1>&b=<num2>:接受 GET 请求,返回两个数的乘积。
/divide?a=<num1>&b=<num2>:接受 GET 请求,返回两个数的商。如果除数为 0,返回适当
的错误信息。
/healthz:健康检查端点,返回 HTTP 状态码 200,用于 Kubernetes 的存活性探测。
(二)将这个 Python 应用容器化,创建一个 Docker 镜像。
在 Kubernetes 中为这个服务创建一个 Deployment。Deployment 应该有以下特点:
- 初始 replica 数量为 3。
- 使用上一步创建的 Docker 镜像。
- 为容器定义 CPU 请求和限制,如请求 0.1 CPU,限制 0.5 CPU。
- 定义存活性探测,使用/healthz 端点。
- 为这个 Deployment 创建一个 Service,类型为 LoadBalancer。
配置一个 Horizontal Pod Autoscaler(HPA)。HPA 应该有以下特点:
- 针对上一步创建的 Deployment。
- 最小 replica 数量为 3,最大 replica 数量为 10。
- 当 CPU 使用率超过 50%时,自动扩展 replica 数量。
(三)编写一个 Python 脚本
使用 http.client 模块向/add, /subtract, /multiply, 和 /divide 端点发送大量请求,模拟高负载情况。
脚本应该:
- 接受服务的 URL 和并发请求数量作为参数。
- 持续发送请求,直到手动停止。
- 打印每秒的请求数量和响应时间统计信息。
- 运行负载测试脚本,观察以下现象:
- Pod 的 CPU 使用率如何变化。
- HPA 如何自动调整 replica 数量。
- 服务的响应时间如何变化。
- (可选)尝试不同的 HPA 配置,如调整 CPU 使用率阈值,观察对自动扩展行为的影响。
(四)目的
- 学习如何使用 Python 的 http.server 模块开发一个简单的 API 服务。
- 学习如何将 Python 应用容器化。
- 学习如何在 Kubernetes 中部署一个无状态的 API 服务,包括 Deployment,Service,以及存
- 活性探测的配置。
- 学习如何配置和使用 Horizontal Pod Autoscaler 进行自动扩展,包括 replica 数量范围
- 和 CPU 使用率阈值的设置。
- 学习如何使用 Python 的 http.client 模块编写一个简单的负载测试脚本。
- 通过观察 Pod 的 CPU 使用率,replica 数量,以及服务响应时间,理解 Kubernetes 如何处理
- 高负载情况。
二、实现步骤
(一)Python计算器服务
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
import json
class CalculatorHandler(BaseHTTPRequestHandler):
def do_GET(self):
# 解析请求路径和查询参数
parsed_path = urlparse(self.path)
query_params = parse_qs(parsed_path.query)
# 根据请求路径调用相应的处理函数
if parsed_path.path == '/add':
self.handle_add(query_params)
elif parsed_path.path == '/subtract':
self.handle_subtract(query_params)
elif parsed_path.path == '/multiply':
self.handle_multiply(query_params)
elif parsed_path.path == '/divide':
self.handle_divide(query_params)
elif parsed_path.path == '/healthz':
self.handle_health_check()
else:
# 未找到路径时返回 404
self.send_response(404)
self.end_headers()
self.wfile.write(b'404 Not Found')
def handle_add(self, query_params):
# 从查询参数中获取数字并执行加法操作
num1 = float(query_params.get('a', ['0'])[0])
num2 = float(query_params.get('b', ['0'])[0])
result = num1 + num2
# 发送响应
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'result': result}
self.wfile.write(json.dumps(response).encode())
def handle_subtract(self, query_params):
# 从查询参数中获取数字并执行减法操作
num1 = float(query_params.get('a', ['0'])[0])
num2 = float(query_params.get('b', ['0'])[0])
result = num1 - num2
# 发送响应
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'result': result}
self.wfile.write(json.dumps(response).encode())
def handle_multiply(self, query_params):
# 从查询参数中获取数字并执行乘法操作
num1 = float(query_params.get('a', ['0'])[0])
num2 = float(query_params.get('b', ['0'])[0])
result = num1 * num2
# 发送响应
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'result': result}
self.wfile.write(json.dumps(response).encode())
def handle_divide(self, query_params):
# 从查询参数中获取数字并执行除法操作
num1 = float(query_params.get('a', ['0'])[0])
num2 = float(query_params.get('b', ['0'])[0])
if num2 == 0:
# 处理除数为零的情况
self.send_response(400)
self.end_headers()
self.wfile.write(b'Division by zero is not allowed')
return
result = num1 / num2
# 发送响应
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'result': result}
self.wfile.write(json.dumps(response).encode())
def handle_health_check(self):
# 处理健康检查请求
self.send_response(200)
self.end_headers()
def run(server_class=HTTPServer, handler_class=CalculatorHandler, port=8000):
# 启动服务器
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print(f'Starting server on port {port}...')
httpd.serve_forever()
if __name__ == '__main__':
run()
(二)构建镜像
1. 构建Dockerfile
# 基础镜像
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 复制应用程序代码到容器中
COPY calculator_api.py .
# 安装依赖
RUN pip install flask
# 暴露端口
EXPOSE 8000
# 运行应用程序
CMD ["python", "calculator_api.py"]
2. 服务打包为docker镜像
docker build -t calculator-api .
3. 推送镜像
#登录自己的docker仓库(有时需要科学上网)
docker login 1145285312@qq.com
#修改刚才打包镜像的tag
docker tag calculator-api:latest ykhfive/calculator-api:latest
#推送镜像
docker push ykhfive/calculator-api:latest
登录成功截图:
修改标签
推送成功截图:
(三)在 Kubernetes 中创建一个 Deployment 和一个 Service
1. 下面是 Deployment 的 YAML 文件示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: calculator-api-deployment
spec:
replicas: 3
selector:
matchLabels:
app: calculator-api
template:
metadata:
labels:
app: calculator-api
spec:
containers:
- name: calculator-api
image: ykhfive/calculator-api:latest # 替换为你的 Docker 镜像地址
resources:
requests:
cpu: "0.1"
limits:
cpu: "0.5"
ports:
- containerPort: 8000
livenessProbe:
httpGet:
path: /healthz
port: 8000
---
apiVersion: v1
kind: Service
metadata:
name: calculator-api-service
spec:
type: LoadBalancer
selector:
app: calculator-api
ports:
- protocol: TCP
port: 80
targetPort: 8000
2. 应用到K8S
kubectl apply -f calculator-api-deployment.yaml
3. 为该 Deployment 创建一个 Horizontal Pod Autoscaler (HPA)
下面是 HPA 的 YAML 文件示例:
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: calculator-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: calculator-api-deployment
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
而后应用HPA
kubectl apply -f calculator-api-hpa.yaml
可能的出现的错误:
启动后如果出现 污点问题,导致pod启动不成功,日志中出现以下错误
nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.
解决:执行以下命令,修改为允许污点
kubectl taint nodes --all node-role.kubernetes.io/master-
而后运行成功
4. 测试服务是否可用
查看下服务的访问地址:
kubectl get svc
看到运行在 32359端口
测试下服务能不能用:访问以下地址
http://localhost:32359/add?a=2&b=3
或(改为自己ip)
http://192.168.6.220:32359/add?a=2&b=3
都能成功访问
(四)编写一个 Python 脚本
使用 http.client 模块向/add, /subtract, /multiply, 和 /divide 端点发送大量请求,模拟高负载情况。
脚本应该:
接受服务的 URL 和并发请求数量作为参数。 持续发送请求,直到手动停止。 打印每秒的请求数量和响应时间统计信息。
python脚本:
import http.client
import time
import threading
import sys
class LoadTestThread(threading.Thread):
def __init__(self, url, endpoint):
threading.Thread.__init__(self)
self.url = url
self.endpoint = endpoint
self.num_requests = 0
self.start_time = time.time()
def run(self):
while True:
try:
# 建立 HTTP 连接
conn = http.client.HTTPConnection(self.url)
# 发送请求
conn.request("GET", f"/{self.endpoint}?a=5&b=3")
# 获取响应
response = conn.getresponse()
response.read()
# 关闭连接
conn.close()
# 统计请求数量
self.num_requests += 1
# 计算每秒的请求数量和响应时间
elapsed_time = time.time() - self.start_time
reqs_per_sec = self.num_requests / elapsed_time
print(f"Endpoint: {self.endpoint}, Requests/s: {reqs_per_sec:.2f}, Elapsed Time: {elapsed_time:.2f}")
except Exception as e:
print(f"Error: {e}")
break
def main(url, num_threads):
# 创建并启动指定数量的线程
threads = []
for _ in range(num_threads):
for endpoint in ["add", "subtract", "multiply", "divide"]:
thread = LoadTestThread(url, endpoint)
thread.start()
threads.append(thread)
# 等待线程结束
for thread in threads:
thread.join()
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python load_test.py <url> <num_threads>")
sys.exit(1)
url = sys.argv[1]
num_threads = int(sys.argv[2])
main(url, num_threads)
启动脚本,将脚本文件创建后运行
# 参数: url 和 线程数
本地执行:
python3 load_test.py localhost:32359 5
主机执行:
python3 load_test.py 192.168.6.220:32359 5
启动成功:
三、查看CPU占用率的变化
1. 执行kubectl top pod查看
kubectl top pod
如果遇到以下情况
说明 Metrics-server未启动
解决: 需要下一个metrics-server的配置文件
官网地址:
https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
有时候下不下来,直接贴文件在下面了
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
name: system:aggregated-metrics-reader
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
rules:
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- apiGroups:
- ""
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server:system:auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:metrics-server
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 0
template:
metadata:
labels:
k8s-app: metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --metric-resolution=15s
- --kubelet-insecure-tls #表示不验证客户端证书
image: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.6.4 #使用阿里镜像
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 4443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 200Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
volumes:
- emptyDir: {}
name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100
解释:
上面的配置文件相比官网下的文件修改了两个地方:修改点如下(左侧是原来的配置,右侧是修改后的配置):
修改了两个地方都是在Deployment.spec.template.containers路径下:
- args增加参数:- –kubelet-insecure-tls #表示不验证客户端证书
- image改为阿里镜像:registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server:v0.6.4
然后应用metrics-server
kubectl apply -f components.yaml
查看结果:
#用命令查看metrics-server的Pod
kubectl get pod -A | grep metrics-server
metrics-server启动成功
再用命令查看pod CPU占用,就可以看到了
kubectl top pod
也可打开Dashborad看,如下
2. 再运行脚本看看效果
1)CPU使用
2)pod使用CPU
3)线程响应时间
一开始是0.01秒就响应
后面越来越久:
4)replice set自动扩展第10个时挂掉了一个pod
查看到了所有效果变化,实验成功