(k8s-king-king)容器云Python运维开发案例(Python调用Kubernetes API)

举报
yd_254431385 发表于 2023/11/24 13:47:09 2023/11/24
【摘要】 1. 开发环境安装将提供的开发环境软件包k8s_Python_Packages.tar.gz上传至k8s-python-dev镜像虚拟机/root目录下,并解压,命令如下:[root@master ~]# curl -O http://mirrors.douxuedu.com/competition/k8s_Python_Packages.tar.gz[root@master ~]# tar...

1. 开发环境安装

将提供的开发环境软件包k8s_Python_Packages.tar.gz上传至k8s-python-dev镜像虚拟机/root目录下,并解压,命令如下:


[root@master ~]# curl -O http://mirrors.douxuedu.com/competition/k8s_Python_Packages.tar.gz
[root@master ~]# tar -zxf k8s_Python_Packages.tar.gz
[root@master ~]# ll
total 12584
-rw-r--r-- 1 root root 911783424 Aug 15 05:02 image.tar
-rw-r--r-- 1 root root 344352659 Aug 15 06:54 k8s_Python_Packages.tar.gz
drwxr-xr-x 2 root root    4096 Aug 15 02:59 Python_Packages

解压python-3.6.8.tar.gz,并安装python3环境,命令如下:


[root@master ~]# tar -zxf /root/Python_Packages/python-3.6.8.tar.gz
[root@master ~]# yum install -y /root/python-3.6.8/packages/*
[root@master ~]# pip3 --version
pip 9.0.3 from /usr/lib/python3.6/site-packages (python 3.6)

使用pip3命令安装开发环境依赖包,命令如下:


[root@master ~]# cd Python_Packages
[root@master Python_Packages]# pip3 install pip-21.1.3-py3-none-any.whl
[root@master ~]# pip3 install --no-index --find-links=/root/Python_Packages/ -r /root/Python_Packages/requirements.txt

2. 基于Kubernetes Restful API实现Deployment创建

使用所提供的k8s-allinone-dev虚拟机环境。根据上一步骤配置好所需的开发环境,登录默认账号密码为“root/Abc@1234”。

使用Kubernetes Restful API库,在/root目录下,创建api_deployment_manager.py文件,要求编写python代码,代码实现以下任务:

① 编写Python程序实现Deployment资源的创建。Deployment配置信息如下。如果同名Deployment存在,先删除再创建。

② 创建完成后,查询该Deployment的详细信息,执行结果控制台输出,以yaml格式展示。

创建Deployment 的yaml的配置如下:


# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

创建用户并生成token,命令如下:


[root@master ~]# kubectl create serviceaccount admin1 -n kube-system
[root@master ~]# kubectl create clusterrolebinding admin1 --clusterrole=cluster-admin --serviceaccount=kube-system:admin1
[root@master ~]# cat<<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: admin1
  namespace: kube-system
  annotations:
    kubernetes.io/service-account.name: "admin1"
EOF 
[root@master ~]# kubectl describe secret admin1 -n kube-system
eyJhbGciOiJSUzI1NiIsImtpZCI6ImtTS2JRR0ZfeC1PZ05oWUsyWkt2TV9WRjA4VjV4NUdELTl1d3ZGNGNIZTQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbjEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4xIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiM2I1M2MyYzctNTc2YS00MDVlLTg5MDYtYTVhOTg4YmFjNGY1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmFkbWluMSJ9.g6xoY3AgEoAP4t0ZZWDhUncBKXtx6CLsx122ujYArQX5BXS2PeyEdz3n1WJZZAqwbqADLdC1WDjVitQgb0bl1mn_CKaPLXjn5IbNwrpYLOlFVAi60ENFxFHDNGtaIrrS_YAfOUG342t0IBCM87PwYulrYNGNYwY0zrVYE8xWHbr8Ij5wyjh79roAnZWHRQFFicT2fpTvQN7BAPYUilefITgh0qvfXhZkVjA0KRnCTkbGVay5mEIA8uTJx8V9PeyY3TAP_ZBRR_GHAdSIpM47D1l8pUgWyBO_cVpWnHURDgaLMp4s5gCmXkJlbuTC2f07-Patgh7ntHZ4E39N2IA

创建python文件并执行(注意api_token 的格式),命令如下:


[root@master ~]# vi api_deployment_manager.py
# ===========================================
# Copyright Jiangsu One Cloud Technology Development Co. LTD. All Rights Reserved.
# ===========================================
import requests,json,yaml,time
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def get_token(api_token,):
    bearer_token = "bearer " + api_token
    return bearer_token
class dep_manager:
    def __init__(self,node_url,bearer_token):
        self.node_url = node_url
        self.bearer_token = bearer_token
    def create_dep(self,yamlFile,namespace):
        headers = {
            "Content-Type": "application/json",
            "Authorization": self.bearer_token
        }
        with open(yamlFile, encoding='utf-8') as f:
            body = json.dumps(yaml.safe_load(f))
        url = self.node_url + "/apis/apps/v1/namespaces/" + namespace + "/deployments"
        req = json.loads(requests.post(url, headers=headers, data=body, verify=False).text)
        return req

    def get_dep(self,dep_name,namespace):
        headers = {
            "Authorization": self.bearer_token
        }
        url = self.node_url + "/apis/apps/v1/namespaces/" + namespace + "/deployments/" + dep_name
        req = json.loads(requests.get(url, headers=headers, verify=False).text)
        return req
if __name__ == "__main__":
    api_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtTS2JRR0ZfeC1PZ05oWUsyWkt2TV9WRjA4VjV4NUdELTl1d3ZGNGNIZTQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbjEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4xIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiM2I1M2MyYzctNTc2YS00MDVlLTg5MDYtYTVhOTg4YmFjNGY1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmFkbWluMSJ9.g6xoY3AgEoAP4t0ZZWDhUncBKXtx6CLsx122ujYArQX5BXS2PeyEdz3n1WJZZAqwbqADLdC1WDjVitQgb0bl1mn_CKaPLXjn5IbNwrpYLOlFVAi60ENFxFHDNGtaIrrS_YAfOUG342t0IBCM87PwYulrYNGNYwY0zrVYE8xWHbr8Ij5wyjh79roAnZWHRQFFicT2fpTvQN7BAPYUilefITgh0qvfXhZkVjA0KRnCTkbGVay5mEIA8uTJx8V9PeyY3TAP_ZBRR_GHAdSIpM47D1l8pUgWyBO_cVpWnHURDgaLMp4s5gCmXkJlbuTC2f07-Patgh7ntHZ4E39N2IA"
    node_url = "https://10.26.7.60:6443"
    bearer_token = get_token(api_token, )
    dep_m = dep_manager(node_url,bearer_token)
    #create
    dep_create = dep_m.create_dep("nginx-deployment.yaml","default")
    time.sleep(10)
    print(f"create_deployment:{dep_create}")
    #get
    get_dep = dep_m.get_dep("nginx-deployment","default")
    print(f"get_Deployment:{get_dep}")
[root@master ~]# python3 api_deployment_manager.py
------------------------------------------------执行结果----------------------------------------------------------
create deployment:{'kind': 'Deployment', 'apiVersion': 'apps/v1', 'metadata': {'name': 'nginx-deployment', 'namespace': 'default', 'uid': '3b526406-895e-4c05-86a8-636c4e857f70', 'resourceVersion': '65978', 'generation': 1, 'creationTimestamp': '2022-10-11T02:17:46Z', 'labels': {'app': 'nginx'}, 'managedFields': [{'manager': 'python-requests', 'operation': 'Update', 'apiVersion': 'apps/v1', 'time': '2022-10-11T02:17:46Z', 'fieldsType': 'FieldsV1', 'fieldsV1': {'f:metadata': {'f:labels': {'.': {}, 'f:app': {}}}, 'f:spec': {'f:progressDeadlineSeconds': {}, 'f:replicas': {}, 'f:revisionHistoryLimit': {}, 'f:selector': {}, 'f:strategy': {'f:rollingUpdate': {'.': {}, 'f:maxSurge': {}, 'f:maxUnavailable': {}}, 'f:type': {}}, 'f:template': {'f:metadata': {'f:labels': {'.': {}, 'f:app': {}}}, 'f:spec': {'f:containers': {'k:{"name":"nginx"}': {'.': {}, 'f:image': {}, 'f:imagePullPolicy': {}, 'f:name': {}, 'f:ports': {'.': {}, 'k:{"containerPort":80,"protocol":"TCP"}': {'.': {}, 'f:containerPort': {}, 'f:protocol': {}}}, 'f:resources': {}, 'f:terminationMessagePath': {}, 'f:terminationMessagePolicy': {}}}, 'f:dnsPolicy': {}, 'f:restartPolicy': {}, 'f:schedulerName': {}, 'f:securityContext': {}, 'f:terminationGracePeriodSeconds': {}}}}}}]}, 'spec': {'replicas': 3, 'selector': {'matchLabels': {'app': 'nginx'}}, 'template': {'metadata': {'creationTimestamp': None, 'labels': {'app': 'nginx'}}, 'spec': {'containers': [{'name': 'nginx', 'image': 'nginx:1.15.4', 'ports': [{'containerPort': 80, 'protocol': 'TCP'}], 'resources': {}, 'terminationMessagePath': '/dev/termination-log', 'terminationMessagePolicy': 'File', 'imagePullPolicy': 'IfNotPresent'}], 'restartPolicy': 'Always', 'terminationGracePeriodSeconds': 30, 'dnsPolicy': 'ClusterFirst', 'securityContext': {}, 'schedulerName': 'default-scheduler'}}, 'strategy': {'type': 'RollingUpdate', 'rollingUpdate': {'maxUnavailable': '25%', 'maxSurge': '25%'}}, 'revisionHistoryLimit': 10, 'progressDeadlineSeconds': 600}, 'status': {}}
获取到Deployment的信息为{'kind': 'Deployment', 'apiVersion': 'apps/v1', 'metadata': {'name': 'nginx-deployment', 'namespace': 'default', 'uid': '3b526406-895e-4c05-86a8-636c4e857f70', 'resourceVersion': '66033', 'generation': 1, 'creationTimestamp': '2022-10-11T02:17:46Z', 'labels': {'app': 'nginx'}, 'annotations': {'deployment.kubernetes.io/revision': '1'}, 'managedFields': [{'manager': 'python-requests', 'operation': 'Update', 'apiVersion': 'apps/v1', 'time': '2022-10-11T02:17:46Z', 'fieldsType': 'FieldsV1', 'fieldsV1': {'f:metadata': {'f:labels': {'.': {}, 'f:app': {}}}, 'f:spec': {'f:progressDeadlineSeconds': {}, 'f:replicas': {}, 'f:revisionHistoryLimit': {}, 'f:selector': {}, 'f:strategy': {'f:rollingUpdate': {'.': {}, 'f:maxSurge': {}, 'f:maxUnavailable': {}}, 'f:type': {}}, 'f:template': {'f:metadata': {'f:labels': {'.': {}, 'f:app': {}}}, 'f:spec': {'f:containers': {'k:{"name":"nginx"}': {'.': {}, 'f:image': {}, 'f:imagePullPolicy': {}, 'f:name': {}, 'f:ports': {'.': {}, 'k:{"containerPort":80,"protocol":"TCP"}': {'.': {}, 'f:containerPort': {}, 'f:protocol': {}}}, 'f:resources': {}, 'f:terminationMessagePath': {}, 'f:terminationMessagePolicy': {}}}, 'f:dnsPolicy': {}, 'f:restartPolicy': {}, 'f:schedulerName': {}, 'f:securityContext': {}, 'f:terminationGracePeriodSeconds': {}}}}}}, {'manager': 'kube-controller-manager', 'operation': 'Update', 'apiVersion': 'apps/v1', 'time': '2022-10-11T02:17:48Z', 'fieldsType': 'FieldsV1', 'fieldsV1': {'f:metadata': {'f:annotations': {'.': {}, 'f:deployment.kubernetes.io/revision': {}}}, 'f:status': {'f:availableReplicas': {}, 'f:conditions': {'.': {}, 'k:{"type":"Available"}': {'.': {}, 'f:lastTransitionTime': {}, 'f:lastUpdateTime': {}, 'f:message': {}, 'f:reason': {}, 'f:status': {}, 'f:type': {}}, 'k:{"type":"Progressing"}': {'.': {}, 'f:lastTransitionTime': {}, 'f:lastUpdateTime': {}, 'f:message': {}, 'f:reason': {}, 'f:status': {}, 'f:type': {}}}, 'f:observedGeneration': {}, 'f:readyReplicas': {}, 'f:replicas': {}, 'f:updatedReplicas': {}}}, 'subresource': 'status'}]}, 'spec': {'replicas': 3, 'selector': {'matchLabels': {'app': 'nginx'}}, 'template': {'metadata': {'creationTimestamp': None, 'labels': {'app': 'nginx'}}, 'spec': {'containers': [{'name': 'nginx', 'image': 'nginx:1.15.4', 'ports': [{'containerPort': 80, 'protocol': 'TCP'}], 'resources': {}, 'terminationMessagePath': '/dev/termination-log', 'terminationMessagePolicy': 'File', 'imagePullPolicy': 'IfNotPresent'}], 'restartPolicy': 'Always', 'terminationGracePeriodSeconds': 30, 'dnsPolicy': 'ClusterFirst', 'securityContext': {}, 'schedulerName': 'default-scheduler'}}, 'strategy': {'type': 'RollingUpdate', 'rollingUpdate': {'maxUnavailable': '25%', 'maxSurge': '25%'}}, 'revisionHistoryLimit': 10, 'progressDeadlineSeconds': 600}, 'status': {'observedGeneration': 1, 'replicas': 3, 'updatedReplicas': 3, 'readyReplicas': 3, 'availableReplicas': 3, 'conditions': [{'type': 'Available', 'status': 'True', 'lastUpdateTime': '2022-10-11T02:17:48Z', 'lastTransitionTime': '2022-10-11T02:17:48Z', 'reason': 'MinimumReplicasAvailable', 'message': 'Deployment has minimum availability.'}, {'type': 'Progressing', 'status': 'True', 'lastUpdateTime': '2022-10-11T02:17:48Z', 'lastTransitionTime': '2022-10-11T02:17:46Z', 'reason': 'NewReplicaSetAvailable', 'message': 'ReplicaSet "nginx-deployment-746ccc65d8" has successfully progressed.'}]}}

3. 基于Kubernetes Python SDK实现Job创建

在前面已建好的Kubernetes开发环境云平台上。使用Kubernetes python SDK的“kubernetes”Python库,在/root目录下,创建sdk_job_manager.py文件,要求编写python代码,代码实现以下任务:

① 编写Python程序实现Job资源的创建。Job配置信息如下。如果同名Job存在,先删除再创建。

② 创建完成后,查询该Job的详细信息,执行结果控制台输出,以json格式展示。

Job创建yaml的信息如下:


# vi spec-pi-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        imagePullPolicy: IfNotPresent
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4
[root@master ~]# cp /root/.kube/config .
[root@master ~]# ctr -n k8s.io images import image.tar
[root@master ~]# vi sdk_job_manager.py
# ===========================================
# Copyright Jiangsu One Cloud Technology Development Co. LTD. All Rights Reserved.
# ===========================================
import json
import os
import yaml
from kubernetes import client, config
from os import path

class job_manager():
    def __init__(self, config_file):
        config.load_kube_config(config_file)
        self.api = client.BatchV1Api()

    def create_job(self, yamlFile):
        v1 = self.api
        fileNamePath = os.path.split(os.path.realpath(__file__))[0]
        yamlPath = os.path.join(fileNamePath, yamlFile)
        print("-------create job-------------")
        with open(yamlPath, encoding="utf8") as f:
            result = yaml.safe_load(f) 
            resp = v1.create_namespaced_job(namespace="default",body=result)
            print(resp)
    def get_job(self):
        v1 = self.api
        print("-------read job-------------")
        resp = v1.read_namespaced_job(name="pi", namespace="default")
        print(resp)
    def delete_job(self):
        v1 = self.api
        resp = v1.delete_namespaced_job(name="pi", namespace="default", propagation_policy='Background',)
        print(resp)
if __name__ == '__main__':
    job_manager(config_file="config").create_job(yamlFile="spec-pi-job.yaml")
job_manager(config_file="config").get_job()

[root@master ~]# python3 sdk_job_manager.py
------------------------------------------------执行结果----------------------------------------------------------
-------create job-------------
{'api_version': 'batch/v1',
 'kind': 'Job',
 'metadata': {'annotations': None,
              'cluster_name': None,
              'creation_timestamp': datetime.datetime(2023, 8, 15, 5, 44, 32, tzinfo=tzlocal()),
.......
              'name': 'pi',
              'namespace': 'default',
              'owner_references': None,
              'resource_version': '67328',
              'self_link': None,
              'uid': 'd68f1112-a523-4846-836f-c69e3d96b159'},
.......

4. Pod资源的Restful APIs HTTP服务封装

编写Python程序实现Pod资源管理程序,将Pod资源管理的封装成Web服务。

在/root目录下创建pod_server.py程序,实现Pod的增删查改等Web访问操作。http.server的host为localhost,端口8889;程序内部实现Kubernetes认证。

提示说明:Python标准库http.server模块,提供了HTTP Server请求封装。

需要实现的Restful API接口如下:

  • GET /pod/{name} ,查询指定名称{name}的Pod;Response的Body以json格式输出。

  • POST /pod/{yamlfilename} 创建yaml文件名称为{yamlfilename}的Pod;Response的Body以json格式。

使用curl -X GET 127.0.0.1:8889/pod/来测试具体如下:


[root@master ~]# vi python-dev-pod-nginx2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: python-dev-pod-nginx2
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 9081
      protocol: TCP
[root@master ~]# vi api_pod_manager.py
# ===========================================
# Copyright Jiangsu One Cloud Technology Development Co. LTD. All Rights Reserved.
# ===========================================
import requests,json,time,yaml
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def get_token(api_token,):
    bearer_token = "bearer " + api_token
    return bearer_token

class pod_manager:
    def __init__(self,node_url,bearer_token):
        self.node_url = node_url
        self.bearer_token = bearer_token

    def create_pod(self,yamlFile,namespace):
        headers = {
            "Content-Type": "application/json",
            "Authorization": self.bearer_token
        }
        with open(yamlFile,encoding='utf-8')as f:
            body = json.dumps(yaml.safe_load(f))

        url = self.node_url + "/api/v1/namespaces/" + namespace + "/pods"
        req = json.loads(requests.post(url,headers=headers,data=body,verify=False).text)

        return req

    def get_pod(self,pod_name,namespace):
        headers = {
            "Authorization": self.bearer_token
        }
        url = self.node_url + "/api/v1/namespaces/" + namespace + "/pods/" + pod_name
        req = json.loads(requests.get(url,headers=headers,verify=False).text)
        return req

    def delete_pod(self,pod_name,namespace):
        headers = {
            "Authorization": self.bearer_token
        }
        url = self.node_url + "/api/v1/namespaces/" + namespace + "/pods/" + pod_name
        req = requests.delete(url,headers=headers,verify=False)
        resp = json.loads(req.text)
        if req.status_code == 200:
            print(f"delete_pod_success:{resp}",req.status_code)
        else:
            print(f" delete_pod_fail",req.status_code)
        return
def get_api_server_token(api_server_token, node_url):

    # Bearer token
    bearer_token = "bearer " + api_server_token
    return bearer_token

def get_api_pod_manager():
    api_server_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtTS2JRR0ZfeC1PZ05oWUsyWkt2TV9WRjA4VjV4NUdELTl1d3ZGNGNIZTQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbjEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4xIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiM2I1M2MyYzctNTc2YS00MDVlLTg5MDYtYTVhOTg4YmFjNGY1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmFkbWluMSJ9.g6xoY3AgEoAP4t0ZZWDhUncBKXtx6CLsx122ujYArQX5BXS2PeyEdz3n1WJZZAqwbqADLdC1WDjVitQgb0bl1mn_CKaPLXjn5IbNwrpYLOlFVAi60ENFxFHDNGtaIrrS_YAfOUG342t0IBCM87PwYulrYNGNYwY0zrVYE8xWHbr8Ij5wyjh79roAnZWHRQFFicT2fpTvQN7BAPYUilefITgh0qvfXhZkVjA0KRnCTkbGVay5mEIA8uTJx8V9PeyY3TAP_ZBRR_GHAdSIpM47D1l8pUgWyBO_cVpWnHURDgaLMp4s5gCmXkJlbuTC2f07-Patgh7ntHZ4E39N2IA"
    cluster_server_url = "https://10.26.7.60:6443"
    node_url = "https://10.26.7.60:6443"
    bearer_token = get_api_server_token(api_server_token, cluster_server_url)
    pod_m = pod_manager(node_url,bearer_token)
    return pod_m

if __name__ == "__main__":
    api_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtTS2JRR0ZfeC1PZ05oWUsyWkt2TV9WRjA4VjV4NUdELTl1d3ZGNGNIZTQifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbjEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4xIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiM2I1M2MyYzctNTc2YS00MDVlLTg5MDYtYTVhOTg4YmFjNGY1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmFkbWluMSJ9.g6xoY3AgEoAP4t0ZZWDhUncBKXtx6CLsx122ujYArQX5BXS2PeyEdz3n1WJZZAqwbqADLdC1WDjVitQgb0bl1mn_CKaPLXjn5IbNwrpYLOlFVAi60ENFxFHDNGtaIrrS_YAfOUG342t0IBCM87PwYulrYNGNYwY0zrVYE8xWHbr8Ij5wyjh79roAnZWHRQFFicT2fpTvQN7BAPYUilefITgh0qvfXhZkVjA0KRnCTkbGVay5mEIA8uTJx8V9PeyY3TAP_ZBRR_GHAdSIpM47D1l8pUgWyBO_cVpWnHURDgaLMp4s5gCmXkJlbuTC2f07-Patgh7ntHZ4E39N2IA"
    node_url = "https://10.26.7.60:6443"
    
    bearer_token = get_token(api_token,)
    pod_m = pod_manager(node_url,bearer_token)

    # 1delete
    delete_pod = pod_m.delete_pod("python-dev-pod-nginx2", "default")

    #2create
    create_pod = pod_m.create_pod("python-dev-pod-nginx2.yaml","default")
    print(f"create_pods:{create_pod}")

    #3get pod
    get_pod = pod_m.get_pod("python-dev-pod-nginx2","default")
    print(f"get_pods:{get_pod}")

    #4delete
    delete_pod = pod_m.delete_pod("python-dev-pod-nginx2","default")

[root@master ~]# vi pod_server.py
# ===========================================
# Copyright Jiangsu One Cloud Technology Development Co. LTD. All Rights Reserved.
# ===========================================
import requests
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
PORT = 8889
# request handler
import api_pod_manager

pod_m = api_pod_manager.get_api_pod_manager()

class MyHandler(BaseHTTPRequestHandler):
    #Header setting
    def _set_headers(self,content_type):
        self.send_response(200)  # 200 stands for request succeeded
        self.send_header("Content-type", content_type)  # informs requests of the Media type
        self.end_headers()

    def do_GET(self):
        self._set_headers("application/json")

        print(self.path)
        # /pod/name
        if self.path.startswith('/pod/'):
            name = self.path[5:]
            print(name)
            result = pod_m.get_pod(name, "default")
            # converts dictionary to a JSON string
            json_string = json.dumps(result)
            self.wfile.write(json_string.encode(encoding='utf_8'))

        else:#
            json_string = json.dumps({'path': 'home', 'received': 'ok'})
            self.wfile.write(json_string.encode(encoding='utf_8'))
    def do_POST(self):
        self._set_headers("application/json")
        print(self.path)
        if self.path.startswith('/pod/'):
            filename = self.path[5:]
            print(filename)
            result = pod_m.create_pod(filename, "default")
            # converts dictionary to a JSON string
            json_string = json.dumps(result)
            self.wfile.write(json_string.encode(encoding='utf_8'))
    def do_DELETE(self):
        self._set_headers("application/json")
        print(self.path)
        if self.path.startswith('/pod/'):
            name = self.path[10:]
            print(name)
            result = pod_m.delete_pod(name,"default")

def run(server_class=HTTPServer, handler_class=MyHandler, addr="localhost", port=PORT):
    server_address = (addr, port)
    httpd = server_class(server_address, handler_class)
    print(f"Starting httpd server on {addr}:{port}")  # f before string allows special formatting
    httpd.serve_forever()


#start
if __name__ == "__main__":
    print("---------start web ----------------")
    run()
print("---------end web----------------") 

[root@master ~]# python3 api_pod_manager.py
[root@master ~]# python3 pod_server.py
---------start web ----------------
Starting httpd server on localhost:8889

可以使用Postman或者Apifox来测试接口:


[root@master ~]# curl -X GET 127.0.0.1:8889/pod/
{"kind": "PodList", "apiVersion": "v1", "metadata": {"resourceVersion": "80124"}, "items": [{"metadata": {"name": "nginx-pod", "namespace": "default", "uid": "2f464e2f-6b6e-4da0-85c2-d69da8f9db11", "resourceVersion": "80123", "creationTimestamp": "2022-10-11T05:32:17Z", "annotations": {"cni.projectcalico.org/podIP": "10.244.235.214/32", "cni.projectcalico.org/podIPs": "10.244.235.214/32", "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"nginx-pod\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"nginx:1.16\",\"imagePullPolicy\":\"IfNotPresent\",\"name\":\"pod-nginx\"}]}}\n"}, "managedFields": [{"manager": "kubectl-client-side-apply", "operation": "Update", "apiVersion": "v1", "time": "2022-10-11T05:32:17Z", "fieldsType": "FieldsV1", "fieldsV1": {"f:metadata": {"f:annotations": {".": {}, "f:kubectl.kubernetes.io/last-applied-configuration": {}}}, "f:spec": {"f:containers": {"k:{\"name\":\"pod-nginx\"}": {".": {}, "f:image": {}, "f:imagePullPolicy": {}, "f:name": {}, "f:resources": {}, "f:terminationMessagePath": {}, "f:terminationMessagePolicy": {}}}, "f:dnsPolicy": {}, "f:enableServiceLinks": {}, "f:restartPolicy": {}, "f:schedulerName": {}, "f:securityContext": {}, "f:terminationGracePeriodSeconds": {}}}}, {"manager": "calico", "operation": "Update", "apiVersion": "v1", "time": "2022-10-11T05:32:18Z", "fieldsType": "FieldsV1", "fieldsV1": {"f:metadata": {"f:annotations": {"f:cni.projectcalico.org/podIP": {}, "f:cni.projectcalico.org/podIPs": {}}}}, "subresource": "status"}, {"manager": "kubelet", "operation": "Update", "apiVersion": "v1", "time": "2022-10-11T05:32:19Z", "fieldsType": "FieldsV1", "fieldsV1": {"f:status": {"f:conditions": {"k:{\"type\":\"ContainersReady\"}": {".": {}, "f:lastProbeTime": {}, "f:lastTransitionTime": {}, "f:status": {}, "f:type": {}}, "k:{\"type\":\"Initialized\"}": {".": {}, "f:lastProbeTime": {}, "f:lastTransitionTime": {}, "f:status": {}, "f:type": {}}, "k:{\"type\":\"Ready\"}": {".": {}, "f:lastProbeTime": {}, "f:lastTransitionTime": {}, "f:status": {}, "f:type": {}}}, "f:containerStatuses": {}, "f:hostIP": {}, "f:phase": {}, "f:podIP": {}, "f:podIPs": {".": {}, "k:{\"ip\":\"10.244.235.214\"}": {".": {}, "f:ip": {}}}, "f:startTime": {}}}, "subresource": "status"}]}, "spec": {"volumes": [{"name": "kube-api-access-4f2c6", "projected": {"sources": [{"serviceAccountToken": {"expirationSeconds": 3607, "path": "token"}}, {"configMap": {"name": "kube-root-ca.crt", "items": [{"key": "ca.crt", "path": "ca.crt"}]}}, {"downwardAPI": {"items": [{"path": "namespace", "fieldRef": {"apiVersion": "v1", "fieldPath": "metadata.namespace"}}]}}], "defaultMode": 420}}], "containers": [{"name": "pod-nginx", "image": "nginx:1.16", "resources": {}, "volumeMounts": [{"name": "kube-api-access-4f2c6", "readOnly": true, "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"}], "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File", "imagePullPolicy": "IfNotPresent"}], "restartPolicy": "Always", "terminationGracePeriodSeconds": 30, "dnsPolicy": "ClusterFirst", "serviceAccountName": "default", "serviceAccount": "default", "nodeName": "k8s-master", "securityContext": {}, "schedulerName": "default-scheduler", "tolerations": [{"key": "node.kubernetes.io/not-ready", "operator": "Exists", "effect": "NoExecute", "tolerationSeconds": 300}, {"key": "node.kubernetes.io/unreachable", "operator": "Exists", "effect": "NoExecute", "tolerationSeconds": 300}], "priority": 0, "enableServiceLinks": true, "preemptionPolicy": "PreemptLowerPriority"}, "status": {"phase": "Running", "conditions": [{"type": "Initialized", "status": "True", "lastProbeTime": null, "lastTransitionTime": "2022-10-11T05:32:17Z"}, {"type": "Ready", "status": "True", "lastProbeTime": null, "lastTransitionTime": "2022-10-11T05:32:19Z"}, {"type": "ContainersReady", "status": "True", "lastProbeTime": null, "lastTransitionTime": "2022-10-11T05:32:19Z"}, {"type": "PodScheduled", "status": "True", "lastProbeTime": null, "lastTransitionTime": "2022-10-11T05:32:17Z"}], "hostIP": "192.168.100.10", "podIP": "10.244.235.214", "podIPs": [{"ip": "10.244.235.214"}], "startTime": "2022-10-11T05:32:17Z", "containerStatuses": [{"name": "pod-nginx", "state": {"running": {"startedAt": "2022-10-11T05:32:18Z"}}, "lastState": {}, "ready": true, "restartCount": 0, "image": "nginx:1.16", "imageID": "docker-pullable://nginx@sha256:d20aa6d1cae56fd17cd458f4807e0de462caf2336f0b70b5eeb69fcaaf30dd9c", "containerID": "docker://4bc8b2ffa93fe12a30d119fca8ad0225db5ff93e6ab53e90abaf63d067fb06a7", "started": true}], "qosClass": "BestEffort"}}]}

5. Service资源Restful APIs HTTP服务封装

编写Python程序实现Service资源管理程序,将Service资源管理的封装成Web服务。

在/root目录下创建service_server.py程序,实现Service的增删查改等Web访问操作。http.server的host为localhost,端口8888;程序内部实现Kubernetes认证。

提示说明:Python标准库http.server模块,提供了HTTP Server请求封装。

需要实现的Restful API接口如下:

  • GET /services/{name},查询指定名称{name}的Service;Response的Body以json格式输出。

  • POST /services/{yamlfilename} 创建yaml文件名称为{yamlfilename}的Service;Response的Body以json格式,(手工将文件服务器主目录所有*.yaml文件下载到root目录下)。

  • DELETE /services/{name};删除指定名称的Service;Response的Body以json格式。

使用curl -X GET 127.0.0.1:8888/services/kubernetes可以进行测试具体如下:


[root@master ~]# vi python-dev-svc3.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc3
  namespace: default
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30083
  type: NodePort
[root@master ~]# vi api_service_manager.py
# ===========================================
# Copyright Jiangsu One Cloud Technology Development Co. LTD. All Rights Reserved.
# ===========================================
import requests,time
import logging
import os,yaml,json
#-----------logger-----------
#get logger
logger = logging.getLogger(__name__)
# level
logger.setLevel(logging.DEBUG)
# format
format = logging.Formatter('%(asctime)s %(message)s')
# to console
stream_handler  = logging.StreamHandler()
stream_handler .setFormatter(format)
logger.addHandler(stream_handler )
#-----------logger-----------

def get_api_server_token(api_server_token, node_url):

    # Bearer token
    bearer_token = "bearer " + api_server_token
    return bearer_token

class api_service_manager():
    def __init__(self,node_url: str, bearer_token: str):
        self.node_url = node_url
        self.bearer_token = bearer_token
    def create_svc(self, yamlFile, namespace: str):
        headers = {
            "Authorization": self.bearer_token,
            "Content-Type": "application/json"
        }
        with open(yamlFile, encoding="utf8") as f:
            body = json.dumps(yaml.safe_load(f))

        request_url = self.node_url + "/api/v1/namespaces/" + namespace + "/services"
        result = json.loads(requests.post(request_url, data=body, headers=headers, verify=False).text)
        logger.debug(f"return_message:{str(result)}")
        return result

    def get_svc(self,svc_name:str,namespace:str):
        headers = {
            "Authorization": self.bearer_token,
            "pretty" : "true"
        }
        request_url = self.node_url + "/api/v1/namespaces/" + namespace + "/services/" + svc_name
        result = json.loads(requests.get(request_url, headers=headers, verify=False).text)
        logger.debug(f"return_message:{str(result)}")
        return result

    def update_svc(self,svc_name:str,yamlFile,namespace:str):
        headers = {
            "Authorization": self.bearer_token,
            "Content-Type": "application/strategic-merge-patch+json"
        }
        with open(yamlFile, encoding="utf8") as f:
            body = json.dumps(yaml.safe_load(f))
        #        '{"spec": {"ports": [{"port": 80, "targetPort": 8089}]}}'
        # body = {"spec": {"ports": [{"port": 80, "targetPort": 80}]}}

        request_url = self.node_url + "/api/v1/namespaces/" + namespace + "/services/" + svc_name
        resp = requests.put(request_url, data=json.dumps(body), headers=headers, verify=False)
        result = json.loads(resp.text)
        logger.debug(f"return_message:{str(result)}")
        return result
    
    def delete_svc(self,svc_name:str,namespace:str):
        headers = {
            "Authorization": self.bearer_token
        }
        request_url = self.node_url + "/api/v1/namespaces/" + namespace + "/services/" + svc_name
        result = json.loads(requests.delete(request_url, headers=headers, verify=False).text)
        logger.debug(f"return_message:{str(result)}")
        return result

def get_api_service_manager():
    api_server_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6InNaSXRNZzEtLXlvT3BwOGdobmhGdmNZeVJsNEpHUGJnWG9nSG1lM2tyMDgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbjEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4xIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiODg4ZDBlMDEtYTNmYi00NmI0LWE5NmMtZDNjNWYwMmM3ZTQyIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmFkbWluMSJ9.Cq31PTl2Khk9vmrpARg3gJVRFAUvatvS7qprfe4Z1qXnf8jYR7wvyNR6VV5TD7cXSy74j-tRo8Cr3x7Ds_dyMKhwHju2uVH_Ioo-zaKaJBpWhC0CmhS-f1KnC9qh4NK8OxO56affZEY0won-5lp4ekEtmGBxeF9_JooB-4pIck56MNdfFKO47fW9VugLW8rcGK-RcV5zoeWc7Eysl6n933K_Q-MQZgVQo9mU2v2V9nEzOmEz-nlKvrgcDFpwP3ZwSLrlOi5b78Qo1VEdiN-VZ_RBWSmizGBY8YCagQJSmIDE3T6bhnMJGYZqGiqyKEaFFQ7v2OpPY0YGtpQnOk8qDA"
    cluster_server_url = "https://10.26.7.60:6443"
    bearer_token = get_api_server_token(api_server_token, cluster_server_url)
    svc_m = api_service_manager(cluster_server_url, bearer_token)
    return svc_m
if __name__ == "__main__":
    api_server_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6InNaSXRNZzEtLXlvT3BwOGdobmhGdmNZeVJsNEpHUGJnWG9nSG1lM2tyMDgifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbjEiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYWRtaW4xIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiODg4ZDBlMDEtYTNmYi00NmI0LWE5NmMtZDNjNWYwMmM3ZTQyIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmUtc3lzdGVtOmFkbWluMSJ9.Cq31PTl2Khk9vmrpARg3gJVRFAUvatvS7qprfe4Z1qXnf8jYR7wvyNR6VV5TD7cXSy74j-tRo8Cr3x7Ds_dyMKhwHju2uVH_Ioo-zaKaJBpWhC0CmhS-f1KnC9qh4NK8OxO56affZEY0won-5lp4ekEtmGBxeF9_JooB-4pIck56MNdfFKO47fW9VugLW8rcGK-RcV5zoeWc7Eysl6n933K_Q-MQZgVQo9mU2v2V9nEzOmEz-nlKvrgcDFpwP3ZwSLrlOi5b78Qo1VEdiN-VZ_RBWSmizGBY8YCagQJSmIDE3T6bhnMJGYZqGiqyKEaFFQ7v2OpPY0YGtpQnOk8qDA"
    cluster_server_url = "https://10.26.7.60:6443"
    bearer_token = get_api_server_token(api_server_token, cluster_server_url)
    svc_m = api_service_manager(cluster_server_url,bearer_token)
    namespace = "default"
    svc_name = "nginx-svc3"
    yaml_file = "python-dev-svc3.yaml"

    #1delete svc
    svc_m.delete_svc(svc_name,namespace)

    #2create svc
    print("crate ----------------")
    svc_m.create_svc(yaml_file, namespace)

    #3get svc
    print("get ----------------")
    svc_m.get_svc(svc_name,namespace)

    #4delete svc
    svc_m.delete_svc(svc_name,namespace)
 [root@master ~]# vi service_server.py
# ===========================================
# Copyright Jiangsu One Cloud Technology Development Co. LTD. All Rights Reserved.
# ===========================================
import requests
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
PORT = 8888
# request handler
import api_service_manager
svc_m = api_service_manager.get_api_service_manager()
class MyHandler(BaseHTTPRequestHandler):
    #Header setting
    def _set_headers(self,content_type):
        self.send_response(200)  # 200 stands for request succeeded
        self.send_header("Content-type", content_type)  # informs requests of the Media type
        self.end_headers()

    def do_GET(self):
        self._set_headers("application/json")

        print(self.path)
        # /services/name
        if self.path.startswith('/services/'):
            name = self.path[10:]
            print(name)
            result = svc_m.get_svc(name, "default")
            # converts dictionary to a JSON string
            json_string = json.dumps(result)
            self.wfile.write(json_string.encode(encoding='utf_8'))

        else:#
            json_string = json.dumps({'path': 'home', 'received': 'ok'})
            self.wfile.write(json_string.encode(encoding='utf_8'))

    def do_POST(self):
        print("post")
        self._set_headers("application/json")
        print(self.path)
        if self.path.startswith('/services/'):
            filename = self.path[10:]
            print("filename---", filename)
            result = svc_m.create_svc(filename, "default")
            # converts dictionary to a JSON string
            print("result---", result)
            json_string = json.dumps(result)
            self.wfile.write(json_string.encode(encoding='utf_8'))
    def do_DELETE(self):
        self._set_headers("application/json")
        print(self.path)
        if self.path.startswith('/services/'):
            name = self.path[10:]
            print(name)
            result = svc_m.delete_svc(name,"default")

def run(server_class=HTTPServer, handler_class=MyHandler, addr="localhost", port=PORT):
    server_address = (addr, port)
    httpd = server_class(server_address, handler_class)
    print(f"Starting httpd server on {addr}:{port}")  # f before string allows special formatting
    httpd.serve_forever()

#start
if __name__ == "__main__":
    print("---------start----------------")
    thread = threading.Thread(target=run)
    thread.start()
print("---------end----------------")

[root@master ~]# python3 api_service_manager.py
[root@master ~]# python3 service_server.py
---------start----------------
---------end----------------
Starting httpd server on localhost:8888
[root@master ~]# curl -X GET 127.0.0.1:8888/services/kubernetes
------------------------------------------------执行结果----------------------------------------------------------
2022-10-11 13:27:02,020 返回信息:{'kind': 'ServiceList', 'apiVersion': 'v1', 'metadata': {'resourceVersion': '79731'}, 'items': [{'metadata': {'name': 'kubernetes', 'namespace': 'default', 'uid': '5bc730ee-4b7a-4e61-93e8-c89ef3cc7240', 'resourceVersion': '195', 'creationTimestamp': '2022-09-21T02:08:37Z', 'labels': {'component': 'apiserver', 'provider': 'kubernetes'}, 'managedFields': [{'manager': 'kube-apiserver', 'operation': 'Update', 'apiVersion': 'v1', 'time': '2022-09-21T02:08:37Z', 'fieldsType': 'FieldsV1', 'fieldsV1': {'f:metadata': {'f:labels': {'.': {}, 'f:component': {}, 'f:provider': {}}}, 'f:spec': {'f:clusterIP': {}, 'f:internalTrafficPolicy': {}, 'f:ipFamilyPolicy': {}, 'f:ports': {'.': {}, 'k:{"port":443,"protocol":"TCP"}': {'.': {}, 'f:name': {}, 'f:port': {}, 'f:protocol': {}, 'f:targetPort': {}}}, 'f:sessionAffinity': {}, 'f:type': {}}}}]}, 'spec': {'ports': [{'name': 'https', 'protocol': 'TCP', 'port': 443, 'targetPort': 6443}], 'clusterIP': '10.96.0.1', 'clusterIPs': ['10.96.0.1'], 'type': 'ClusterIP', 'sessionAffinity': 'None', 'ipFamilies': ['IPv4'], 'ipFamilyPolicy': 'SingleStack', 'internalTrafficPolicy': 'Cluster'}, 'status': {'loadBalancer': {}}}]}
127.0.0.1 - - [11/Oct/2022 13:27:09] "GET /services/kubernetes HTTP/1.1" 200 -
【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。