K8S集群创建一个自动扩展的计算器 API 服务

也可查看语雀文档地址

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路径下:

  1. args增加参数:- –kubelet-insecure-tls #表示不验证客户端证书
  2. 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

查看到了所有效果变化,实验成功

  • 15
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值