定时自动重启Pod服务


2019年11月09日 03:25:44   6,797 次浏览

方法1:滚动重启

从1.15版开始,Kubernetes允许您滚动重启部署。作为Kubernetes的新增功能,这是最快的重启方法。

kubectl rollout restart deployment [deployment_name]

上面提到的命令将逐步执行关闭操作,并重新启动deployment中的每个pod容器。在重启过程中应用仍然可用,因为大多数容器仍在运行。

方法2:使用环境变量

另一种方法是设置或更改环境变量,以强制Pod重新启动并与您所做的更改同步。

例如,可以更改容器部署日期:

kubectl set env deployment [deployment_name] DEPLOY_DATE="$(date)"

在上面的示例中,该命令set env设置环境变量的更改,deployment [deployment_name]选择您的部署,并DEPLOY_DATE="$(date)"更改部署日期。

方法3:缩放副本数

我们可以使用该scale命令来更改deployment的副本数。将此数量设置为0实际上会关闭容器:

kubectl scale deployment [deployment_name] --replicas=0

要重新启动Pod,请使用相同的命令将副本数设置为大于零的任何值:

kubectl scale deployment [deployment_name] --replicas=1

将副本数设置为0时,Kubernetes会销毁副本。

将数字设置为大于0后,Kubernetes将创建新副本。新副本的名称将与旧副本的名称不同。您可以使用该命令kubectl get pods检查pod的状态,并查看新名称。

上面我们是通过(kubectl rollout重新启动) ,部署创建新的副本,并等待它们启动,然后删除旧的pods,并重新路由流量。服务将不间断地继续。接下来使用cronjob命令配合rollout restart每天定时重启动pod。

在开始之前必须先设置RBAC,以便从集群内部运行的Kubernetes client具有对Kubernetes API执行所需调用的权限。

---
# Service account the client will use to reset the deployment,
# by default the pods running inside the cluster can do no such things.
kind: ServiceAccount
apiVersion: v1
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
---
# allow getting status and patching only the one deployment you want
# to restart
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
rules:
  - apiGroups: ["apps", "extensions"]
    resources: ["deployments"]
    resourceNames: ["<YOUR DEPLOYMENT NAME>"]
    verbs: ["get", "patch", "list", "watch"] # "list" and "watch" are only needed
                                             # if you want to use `rollout status`
---
# bind the role to the service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: deployment-restart
subjects:
  - kind: ServiceAccount
    name: deployment-restart
    namespace: <YOUR NAMESPACE>

cronjob配置:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: deployment-restart
  namespace: <YOUR NAMESPACE>
spec:
  concurrencyPolicy: Forbid
  schedule: '0 8 * * *' # cron spec of time, here, 8 o'clock
  jobTemplate:
    spec:
      backoffLimit: 2 # this has very low chance of failing, as all this does
                      # is prompt kubernetes to schedule new replica set for
                      # the deployment
      activeDeadlineSeconds: 600 # timeout, makes most sense with 
                                 # "waiting for rollout" variant specified below
      template:
        spec:
          serviceAccountName: deployment-restart # name of the service
                                                 # account configured above
          restartPolicy: Never
          containers:
            - name: kubectl
              image: bitnami/kubectl # probably any kubectl image will do,
                                     # optionaly specify version, but this
                                     # should not be necessary, as long the
                                     # version of kubectl is new enough to
                                     # have `rollout restart`
              command:
                - 'kubectl'
                - 'rollout'
                - 'restart'
                - 'deployment/<YOUR DEPLOYMENT NAME>'

如果希望cronjob等待部署完成,可以将cronjob命令更改为:

command:
 - bash
 - -c
 - >-
   kubectl rollout restart deployment/<YOUR DEPLOYMENT NAME> &&
   kubectl rollout status deployment/<YOUR DEPLOYMENT NAME>

Kubernetes Pod扩容与缩容


2019年11月07日 14:06:42   1,654 次浏览

针对不同时期流量的大小我们可以给Pod扩缩容,Kubernetes支持通过kubectl命令手动扩缩容,也支持通过HPA自动横向扩缩容。

手动扩缩容

现有如下deployment配置(nginx-deployment.yml):

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

创建该Deployment:

有3个nginx实例,现在用下面这条命令将nginx实例扩充到5个:

kubectl scale deployment nginx-deployment --replicas 5

缩容:

kubectl scale deployment nginx-deployment --replicas 1

HPA

HPA能够根据特定指标完成目标Pod的自动扩缩容。创建一个与上面nginx-deployment相对应的HPA(nginx-deployment-hpa.yml):

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-deployment-hpa-v1
spec:
  maxReplicas: 6
  minReplicas: 2
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-deployment
  targetCPUUtilizationPercentage: 50

主要参数如下:

  1. scaleTargetRef:目标作用对象,可以是Deployment、ReplicationController或ReplicaSet。
  2. targetCPUUtilizationPercentage:期望每个Pod的CPU使用率都为50%,该使用率基于Pod设置的CPU Request值进行计算,例如该值为200m,那么系统将维持Pod的实际CPU使用值为100m。
  3. minReplicas和maxReplicas:Pod副本数量的最小值和最大值,系统将在这个范围内进行自动扩缩容操作,并维持每个Pod的CPU使用率为50%。

使用HPA,需要预先安装Metrics Server,用于采集Pod的CPU使用率。

Kubernetes Service基础


2019年11月07日 13:29:07   1,467 次浏览

Kubernetes可以为一组具有相同功能的Pod提供一个统一的入口地址,并且将请求均衡的转发到各个对应的Pod上。本节主要记录Service的一些基本用法。

基本用法

创建一个Tomcat RC(tomcat-rc.yml):

apiVersion: v1
kind: ReplicationController
metadata:
  name: tomcat-rc
spec:
  replicas: 2
  selector:
    name: tomcat
  template:
    metadata:
      name: tomcat
      labels:
        name: tomcat
    spec:
      containers:
        - name: tomcat
          image: tomcat
          ports:
            - containerPort: 8080

创建该RC:

我们可以通过Node IP + Container Port在Kubernetes集群中访问Tomcat:

由于Pod是Kubernetes集群范围内的虚拟概念,集群外的客户端无法通过Pod的IP和端口访问,我们可以将Pod的端口号映射到宿主机,以使客户端应用能够通过物理机访问容器应用,修改刚刚的tomcat-rc.yml,添加hostPort:

apiVersion: v1
kind: ReplicationController
metadata:
  name: tomcat-rc
spec:
  replicas: 2
  selector:
    name: tomcat
  template:
    metadata:
      name: tomcat
      labels:
        name: tomcat
    spec:
      containers:
        - name: tomcat
          image: tomcat
          ports:
            - containerPort: 8080
              hostPort: 8081 # 新增

更新该RC:

可以看到tomcat pod被分配到了node1和node2上,所以我们可以在宿主机外使用或者访问tomcat:

但是我们知道,Pod的IP地址是不可靠的,例如当Pod所在的Node发生故障时,Pod将被Kubernetes重新调度到另一个Node,Pod的IP地址将发生变化。所以我们可以定义一个tomcat pod的统一访问入口,这就是Service的作用。

创建Service有两种方式:

1.kubectl expose命令来创建Service

kubectl expose rc tomcat-rc

现在我们就可以通过Service的clusterIP + Port来访问了:

Service地址10.1.187.222:8080均衡的负载到了两个tomcat pod上(10.244.1.19:8080和10.244.4.13:8080)

2.通过配置文件创建

定义一个tomcat-service.yml:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  ports:
    - port: 8081 # service端口
      targetPort: 8080 # 目标端口
  selector:
    name: tomcat # 选择器,选择name=tomcat的pod

创建该Service:

同样,Service默认是不能外部访问的,如果想让外部能够访问到tomcat service,我们也需要将Service的端口映射到物理机,修改上面的tomcat-service.yml:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  type: NodePort # 新增
  ports:
    - port: 8081
      targetPort: 8080
      nodePort: 30000 # 新增
  selector:
    name: tomcat

上面配置通过设置nodePort(范围30000-32767)映射到物理机,同时设置Service的类型为NodePort,创建该Service:

现在我们就可以通过宿主机的IP+30000访问tomcat了:

负载均衡策略

Kubernetes Service提供了两种负载分发策略:RoundRobin和SessionAffinity:

  1. RoundRobin:轮询模式(默认),即轮询将请求转发到后端的各个Pod上。
  2. SessionAffinity:基于客户端IP地址进行会话保持的模式,即第1次将某个客户端发起的请求转发到后端的某个Pod上,之后从相同的客户端发起的请求都将被转发到后端相同的Pod上。可以通过设置service.spec.sessionAffinity=ClientIP来启用SessionAffinity策略:
apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  type: NodePort
  sessionAffinity: ClientIP # 采用SessionAffinity策略
  ports:
    - port: 8081
      targetPort: 8080
      nodePort: 30000
  selector:
    name: tomcat

Headless Service

Headless Service不提供ClusterIP,仅通过Label Selector将后端的Pod列表返回给调用的客户端。

创建tomcat-headless-service.yml:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-headless-service
spec:
  ports:
    - port: 8080
  clusterIP: None # 设置clusterIP为None,表示headless service
  selector:
    name: tomcat

创建该headless service:

Kubernetes Pod升级与回滚


2019年11月05日 14:00:55   1,474 次浏览

当集群中的某个服务需要升级时,我们需要停止目前与该服务相关的所有Pod,然后下载新版本镜像并创建新的Pod。如果集群规模比较大,则这个工作变成了一个挑战,而且先全部停止然后逐步升级的方式会导致较长时间的服务不可用。Kubernetes提供了滚动升级功能来解决上述问题。如果在更新过程中发生了错误,则还可以通过回滚操作恢复Pod的版本。

Deployment升级

现有如下deployment定义(nginx-deployment.yml):

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

创建该deployment:

通过kubectl命令将nginx的版本更新到1.9.1:

kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1

查看滚动升级的过程:

查看Pod,会发现名称已经改变了:

上述滚动升级的过程可以用下图表示:

查看Deployment nginx-deployment的详细事件信息可以证明这一点:

kubectl describe deployments/nginx-deployment

查看rs:

默认的升级策略为RollingUpdate(滚动更新),Kubernetes还支持Recreate(重建)策略:

  1. Recreate:先杀掉所有正在运行的Pod,然后创建新的Pod。
  2. RollingUpdate:以滚动更新的方式来逐个更新Pod。同时,可以通过设置spec.strategy.rollingUpdate下的两个参数(maxUnavailable和maxSurge)来控制滚动更新的过程。

RollingUpdate的两个参数含义如下:

  1. maxUnavailable:用于指定Deployment在更新过程中不可用状态的Pod数量的上限。该maxUnavailable的数值可以是绝对值(例如1)或Pod期望的副本数的百分比(例如10%)。
  2. maxSurge:用于指定在Deployment更新Pod的过程中Pod总数超过Pod期望副本数部分的最大值。该maxSurge的数值可以是绝对值(例如5)或Pod期望副本数的百分比(例如10%)。

此外,除了使用kubectl命令升级外,我们也可以直接通过修改deployment.yml的方式来完成。

Deployment回滚

如果升级后效果不满意的话,我们也可以将Deployment回滚到升级之前,使用下面这条命令查看滚动升级的历史:

kubectl rollout history deployment/nginx-deployment

REVISION为升级的历史版本,我们只完成了nginx从1.7.9升级到1.9.1的过程,所以REVISION 1表示nginx为1.7.9的版本,REVISION 2表示nginx为1.9.1的版本。因为我们在创建deployment的时候没有加上--record=true参数,所以CHANGE-CACSE列是空的。

需要查看特定版本的详细信息,则可以加上–revision=参数:

要将nginx回退到1.7.9版本,我们可以将REVISION指定为1:

kubectl rollout undo deployment/nginx-deployment --to-revision=1

暂停与恢复

因为Deployment配置一旦修改就会触发升级操作,所以当修改的地方较多的时候就会频繁触发升级。我们可以先暂停升级操作,当所有配置都修改好后再恢复升级。

暂停:

kubectl rollout pause deployment/nginx-deployment

暂停后,对nginx-deployment的修改不会触发升级。修改好后,恢复升级:

kubectl rollout resume deployment/nginx-deployment

其他Pod管理对象升级策略

DaemonSet

DaemonSet的升级策略包括两种:OnDelete和RollingUpdate:

  1. OnDelete(默认):在创建好新的DaemonSet配置之后,新的Pod并不会被自动创建,直到用户手动删除旧版本的Pod,才触发新建操作。
  2. RollingUpdate:和前面介绍的一致,不过不支持rollout,只能通过将配置改回去来实现。

StatefulSet

支持Recreate、OnDelete和RollingUpdate。

Kubernetes Pod管理对象与调度策略


2019年11月04日 13:04:40   1,512 次浏览

在前面的学习中我们了解到,在Kubernetes中,Pod管理对象主要有RC(RS)、Deployment、StatefulSet、DaemonSet和Job(CronJob)等。其中RC(RS)和Deployment的用法已经大致了解,这里主要记录下StatefulSet、DaemonSet和Job(CronJob)的用法。默认情况下,Pod管理对象在创建Pod的时候是根据系统自动调度算法来完成部署的,我们可以设置调度策略来实现Pod的精准调度。

Pod调度策略

NodeSelector

我们可以该某个节点Node设置标签,然后通过NodeSelector让Pod调度到该节点上。

前面搭建的Kubernetes集群有两个worker节点node1和node2,我们给node2打个标签:

kubectl label nodes node2 tier=frontend

接着定义一个RC配置(node-select-pod.yml):

apiVersion: v1
kind: ReplicationController
metadata:
  name: test-rc
spec:
  replicas: 1
  selector:
    name: nginx
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
      nodeSelector:
        tier: frontend

创建该RC,观察Pod最终调度的节点:

可以看到nginx pod已经成功调度到了node2节点上。

Kubernetes会给每个node贴上一些默认的标签,通过kubectl get node node1 -o yaml可以看到这些默认的标签:

beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: node1
kubernetes.io/os: linux

这些默认的标签在下面这些调度策略中也是蛮常用的。

NodeAffinity

Affinity[əˈfɪnəti]:亲和力,喜好。NodeAffinity为Node亲和力调度,主要有两个规则(名称有点长,快18cm了吧):

  1. RequiredDuringSchedulingIgnoredDuringExecution:必须满足指定的规则才可以调度Pod,硬规则。
  2. PreferredDuringSchedulingIgnoredDuringExecution:软规则,最好满足所列出的条件才调度Pod。

IgnoredDuringExecution的意思是,如果一个Pod已经调度到某个节点上了,然后这个节点的标签发生了改变,那么不影响已经调度好的Pod,ignore。

定义一个配置文件(node-affinity.yml),用于演示NodeAffinity:

apiVersion: v1
kind: ReplicationController
metadata:
  name: test-rc
spec:
  replicas: 1
  selector:
    name: nginx
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: beta.kubernetes.io/arch
                    operator: In
                    values:
                      - amd64
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 1
              preference:
                matchExpressions:
                  - key: disk-type
                    operator: In
                    values:
                      - ssd

上面配置使得RC在调度nginx pod的时候需要满足:节点具有beta.kubernetes.io/arch=amd64标签,如果具有disk-type=ssd标签就更好了,换句话说就是希望nginx pod调度在cpu架构为amd,磁盘为ssd的节点上。

上面配置中,操作符operator除了可以使用In外,还可以使用NotIn、Exists、DoesNotExist、Gt、Lt。

NodeAffinity规则设置需要注意的几个点:

  1. 如果nodeAffinity指定了多个nodeSelectorTerms,那么其中一个能够匹配成功即可;
  2. 如果在nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod。

运行上面这个配置:

可以看到,因为node1和node2都具有beta.kubernetes.io/arch=amd64标签,而没有disk-type=ssd标签,所以nginx pod有可能被调度到node1也有可能被调度到node2。

我们给node1添加disk-type=ssd标签,重新运行上面的配置文件:

可以看到,nginx pod被调度到了node1上。

PodAffinity

PodAffinity指的是Pod亲和力调度股则,比如某些节点已经存在一些Pod了,新的Pod在调度的时候希望和指定Pod在一台节点上,亦或有意避开和指定Pod在一台节点上,这时候就可以用PodAffinity实现。

NodeAffinity规则设置也是通过requiredDuringSchedulingIgnoredDuringExecutionPreferredDuringSchedulingIgnoredDuringExecution实现的。此外,NodeAffinity还需要设置topology(拓扑规则),意为表达节点所属的topology范围:

  1. kubernetes.io/hostname(节点所处的服务器)
  2. failure-domain.beta.kubernetes.io/zone(节点所处的服务器云盘分区)
  3. failure-domain.beta.kubernetes.io/region(节点所处的服务器云盘所在的地区)

要演示PodAffinity的使用,需要先创建一个参照Pod,创建一个参照Pod的配置文件(flag-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: flag-pod
  labels:
    tier: frontend
    app: flag-nginx
spec:
  containers:
    - name: flag-nginx
      image: nginx
      ports:
        - containerPort: 8081

这个Pod具有tier=frontendapp=flag-nginx标签。

创建这个参照Pod:

参照pod运行在了node1节点上。

创建好后,接着定义一个新的配置类(pod-affinity.yml):

apiVersion: v1
kind: Pod
metadata:
  name: pod-affinity
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: tier
                operator: In
                values:
                  - frontend
          topologyKey: kubernetes.io/hostname

上述配置要求,nginx pod需要和标签包含tier=frontend的Pod分配在同一个Node上(topologyKey: kubernetes.io/hostname),创建该Pod,观察它被调度到哪个节点上了:

podAntiAffinity和podAffinity刚好相反,举个例子,创建一个新的配置类(pod-affinity-two.yml):

apiVersion: v1
kind: Pod
metadata:
  name: pod-affinity-two
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
              - key: app
                operator: In
                values:
                  - flag-nginx
          topologyKey: kubernetes.io/hostname

上面配置希望nginx pod不和app=flag-nginx的Pod在同一个节点。

创建该Pod,观察它被调度到哪个节点上了:

可以看到,它和flag-nginx处于不同的节点,位于node2。

Taints & Tolerations

Taints用于给节点添加污点,而Tolerations用于定义Pod对节点污点的容忍度。在Node上设置一个或多个Taint之后,除非Pod明确声明能够容忍这些污点,否则无法在这些Node上运行。Toleration是Pod的属性,让Pod能够(注意,只是能够,而非必须)运行在标注了Taint的Node上。

给节点设置污点的语法为:

kubectl taint nodes [nodeName] key=value:rule

其中rule的取值有:

  1. NoScheudle:不调度;
  2. PreferNoSchedule:最好不要调度;
  3. NoExecute:不运行;
  4. PreferNoExecute:最好不运行。
  • NoSchedule不调度,如果是在调度后设置的污点,并且Pod没有容忍该污点,也能继续执行
  • NoExecute不执行,如果是在调度后设置的污点,并且Pod没有容忍该污点,则会被驱逐。可以设置驱逐时间tolerationSeconds: xx

NoSchedule和NoExecute区别:

我们给node1节点设置一个污点:

kubectl taint nodes node1 aa=bb:NoSchedule

然后新建一个Pod配置类(pod-taint-test.yml):

apiVersion: v1
kind: Pod
metadata:
  name: pod-taint
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  tolerations:
    - key: "aa"
      operator: "Equal"
      value: "bb"
      effect: "NoSchedule"

Pod的Toleration声明中的key和effect需要与Taint的设置保持一致,并且满足以下条件之一:

  1. operator的值是Exists(无须指定value),如:
......
  tolerations:
    - key: "aa"
      operator: "Exists"
      effect: "NoSchedule"
  1. operator的值是Equal并且value相等。如果不指定operator,则默认值为Equal。

另外,有如下两个特例:

  1. 空的key配合Exists操作符能够匹配所有的键和值;
  2. 空的effect匹配所有的effect。

回到pod-taint-test.yml,该配置文件定义nginx pod可以容忍aa=bb:NoSchedule这个污点,所以它有可能会被调度到node1上。创建该Pod,观察:

这时候在nginx pod所运行的节点node1上新增一个污点:

kubectl taint nodes node1 cc=dd:NoExecute

观察nginx pod情况:

可以看到它被驱逐了,已经没有正在运行的pod了。

Pod Priority Preemption

我们可以给Pod指定优先级,优先级可以通过PriorityClass对象创建,比如创建一个优先级为10000的PriorityClass:

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: hign-priority
value: 10000
globalDefault: false
description: "10000优先级"

上述文件定义了一个名为high-priority的优先级类别,优先级为10000,数字越大,优先越高。超过一亿的数字被系统保留,用于指派给系统组件。

优先级创建后,可以在Pod定义中引用该优先级:

...
spec:
  containers:
    - name: nginx
      image: nginx
      ports:
        - containerPort: 80
  priorityClassName: hign-priority

Pod管理对象

Job

Job是一种特殊的Pod管理对象,是一种一次性Pod运行任务,任务结束后,Pod的生命周期也就结束了。

定义一个Job配置类(job.yml):

apiVersion: batch/v1
kind: Job
metadata:
  name: job-ex
spec:
  template:
    spec:
      containers:
        - name: job
          image: busybox
          command: ["echo", "hello job"]
      restartPolicy: Never

Job的重启策略restartPolicy只支持Never和OnFailure。

创建这个Job:

查看Job状态:

COMPLETIONS 1/1表示总共需要执行1个任务,共执行完1个任务。

查看对应Pod的状态:

状态为Completed。

查看Job日志:

Job还可以设置并发数量和总Job数,修改上面的job.yml:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-ex
spec:
  parallelism: 2 # 并发数2
  completions: 6 # 总的Job数量
  template:
    spec:
      containers:
        - name: job
          image: busybox
          command: ["echo", "hello job"]
      restartPolicy: Never

运行该Job:

查看Pod:

CronJob

CronJob顾名思义就是支持Cron表达式的Job,不过Kubernetes的Cron表达式和传统的Cron表达式不太一样,不支持到秒级。具体规则如下:

Minutes Hours DayofMonth Month DayofWeek Year
  1. Minutes:可出现, - * / 这4个字符,有效范围为0~59的整数。
  2. Hours:可出现, - * /这4个字符, 有效范围为0~23的整数。
  3. DayofMonth:可出现, - * / ? L W C 这8个字符,有效范围为0~31的整数。
  4. Month:可出现, - * /这4个字符,有效范围为1~12的整数或JAN~DEC。
  5. DayofWeek:可出现, - * / ? L C 这8个字符,有效范围为1~7的整数或SUN~SAT。1表示星期天,2表示星期一,以此类推。

上面特殊字符的含义如下:

  1. *:表示匹配该域的任意值,假如在Minutes域使用*,则表示每分钟都会触发事件。
  2. /:表示从起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域设置为5/20,则意味着第1次触发在第5min时,接下来每20min触发一次,将在第25min、第45min等时刻分别触发。

定义一个CronJob的配置类(cron-job.yml):

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: cron-job-ex
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: hello
              image: busybox
              command: ["sh", "-c", "date;echo hello cronJob"]
          restartPolicy: Never

上面的定时任务每分钟执行一次。

创建该CronJob:

过个两三分钟查看运行情况:

DaemonSet

DaemonSet适用于在每个Node都需要运行一个Pod的时候使用,比如:每一个Node上运行一个日志采集、性能监控的Pod。

比如我们现在需要在每个节点上都部署一个node-exporter来采集节点信息,可以通过DaemonSet来实现。

定义一个DaemonSet配置文件(daemonset.yml):

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter-d
spec:
  selector:
    matchLabels:
      name: node-exporter
  template:
    metadata:
      labels:
        name: node-exporter
    spec:
      containers:
        - name: node-exporter
          image: prom/node-exporter
          ports:
            - containerPort: 9100

创建该DaemonSet之前先删除上面在node1节点上创建的污点:

kubectl taint nodes node1 aa:NoSchedule-
kubectl taint nodes node1 cc:NoExecute-

创建该DaemonSet,然后观察Pod信息,看是否每个节点都部署了一个实例:

Kubernetes Pod基础


2019年11月03日 18:09:07   1,504 次浏览

Pod是Kubernetes的最小调度单位,包含一个或者多个容器(比如Docker容器),容器间共享网络和存储。这节主要记录什么是静态Pod,Pod容器如何共享存储,如何使用ConfigMap管理Pod配置,如何使用Downward API获取Pod信息等。

静态Pod

静态Pod是由kubelet创建并管理的特殊的Pod,无法和Pod管理对象关联,并且不能通过API Server关联。创建静态Pod有配置文件方式和HTTP方式:

配置文件方式

在搭建Kubernetes集群的时候,从启动Master节点的日志可以看出,静态Pod的目录位于/etc/kubernetes/manifests

在该目录下创建静态Pod文件:

cd /etc/kubernetes/manifests
vim static-pod.yaml

内容如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: static-pod
  labels:
    name: static-pod
spec:
  containers:
    - name: static-nginx
      image: nginx
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 80

过了一会查看Pod:

于静态Pod无法通过API Server直接管理,所以在Master上尝试删除这个Pod时,会使其变成Pending状态,且不会被删除。

删除该Pod的操作只能是到其所在Node上将其定义文件static-pod.yaml从/etc/kubernetes/manifests目录下删除:

HTTP方式

过设置kubelet的启动参数--manifest-url,kubelet将会定期从该URL地址下载Pod的定义文件,并以.yaml或.json文件的格式进行解析,然后创建Pod。

Pod容器共享Volume

同一个Pod的多个容器间可以共享Pod级别的Volume,举个例子:

vim pod-volume.yml

内容如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume
spec:
  containers:
    - name: tomcat
      image: tomcat
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
      volumeMounts:
        - mountPath: /usr/local/tomcat/logs
          name: logs
    - name: busybox
      image: busybox
      imagePullPolicy: IfNotPresent
      command: ["sh", "-c", "tail -f /logs/catalina*.log"]
      volumeMounts:
        - mountPath: /logs
          name: logs
  volumes:
    - name: logs
      emptyDir: {}

上面Pod定义中,创建了一个Pod级别的Volume,名称为logs,类型为emptyDir。这个Volume同时挂载到了tomcat的/usr/local/tomcat/logs目录下,也挂载到了busybox的/logs目录下。

创建该Pod:

kubectl create -f pod-volume.yml

这里的tomcat镜像比较大,大概有500MB左右,所以在创建之前,最好在Kubernetes集群的每个节点中配置Docker镜像加速地址。

当pod-volume状态为ready后,查看busybox的日志:

该日志为tomcat的启动日志,说明上面挂载的Volume生效了,可以通过查看tomcat/usr/local/tomcat/logs目录下和busybox/logs目录下的内容来证明这一点:

ConfigMap

ConfigMap以一个或多个key:value的形式保存在Kubernetes系统中供应用使用,既可以用于表示一个变量的值(例如version=v1),也可以用于表示一个完整配置文件的内容(例如server.xml=<?xml...>...)。

创建ConfigMap

创建ConfigMap主要有两种方式:

1.通过yml文件创建

创建simple-cm.yml文件,内容如下所示:

apiVersion: v1
kind: ConfigMap
metadata:
  name: simple-cm
data:
  version: v1
  release: stable

该ConfigMap仅包含两个简单的值version和releases。

创建该ConfigMap:

kubectl create -f simple-cm.yml

查看该ConfigMap:

在定义ConfigMap的时候,value除了可以使用简单的值外,还可以是整个配置文件的内容。

创建file-cm.yml,内容如下所示:

apiVersion: v1
kind: ConfigMap
metadata:
  name: file-cm
data:
  serverXml: |
    <?xml version="1.0" encoding="UTF-8"?>
    <Server port="8005" shutdown="SHUTDOWN">
      <Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
      <Listener SSLEngine="on" className="org.apache.catalina.core.AprLifecycleListener"/>
      <!-- Prevent memory leaks due to use of particular java/javax APIs-->
      <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
      <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
      <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
      <GlobalNamingResources>
        <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>
      </GlobalNamingResources>
      <Service name="Catalina">
        <Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
        <Engine defaultHost="localhost" name="Catalina">
          <Realm className="org.apache.catalina.realm.LockOutRealm">
            <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
          </Realm>

          <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">
            <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t &quot;%r&quot; %s %b" prefix="localhost_access_log" suffix=".txt"/>
          <Context docBase="D:\Code\apache-tomcat-8.5.32\webapps\tj_certification" path="/tj_certification" reloadable="true" source="org.eclipse.jst.jee.server:tj_certification"/></Host>
        </Engine>
      </Service>
    </Server>
  serverProperties: "1catalina.org.apache.juli.AsyncFileHandler.level = FINE
                     1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.

                     2localhost.org.apache.juli.AsyncFileHandler.level = FINE
                     2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.

                     3manager.org.apache.juli.AsyncFileHandler.level = FINE
                     3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     3manager.org.apache.juli.AsyncFileHandler.prefix = manager.

                     4host-manager.org.apache.juli.AsyncFileHandler.level = FINE
                     4host-manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
                     4host-manager.org.apache.juli.AsyncFileHandler.prefix = host-manager.

                     java.util.logging.ConsoleHandler.level = FINE
                     java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter

                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.AsyncFileHandler

                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.AsyncFileHandler

                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO
                     org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.AsyncFileHandler"

创建该ConfigMap:

2.直接通过Kubectl命令创建

通过kubectl命令创建ConfigMap主要有以下三种用法:

  1. 通过–from-file参数从文件中进行创建,可以指定key的名称,也可以在一个命令行中创建包含多个key的ConfigMap,语法为:
kubectl cerate configmap [NAME] --from-file=[key=]source --from-file=[key=]source

通过–from-file参数从目录中进行创建,该目录下的每个配置文件名都被设置为key,文件的内容被设置为value,语法为:

kubectl cerate configmap [NAME] --from-file=config-file-dir

使用–from-literal时会从文本中进行创建,直接将指定的key#=value#创建为ConfigMap的内容,语法为:

kubectl cerate configmap [NAME] --from-literal=key1=value1 --from-literal=key2=value2

比如使用kubectl命令创建一个和simple-cm效果一样的ConfigMap:

使用kubectl命令创建一个和file-cm效果一样的ConfigMap:

首先在当前目录下准备好两个配置文件server.xml和server.properties:

然后使用kubectl命令创建:

Pod容器使用ConfigMap

Pod的容器要使用ConfigMap主要有两种方式:

  1. 通过环境变量获取ConfigMap中的内容;
  2. 通过Volume挂载的方式将ConfigMap中的内容挂载为容器内部的文件或目录。

通过环境变量的方式

创建一个Pod配置(simple-cm-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: simple-cm-pod
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "env | grep ENV"] # 打印名称包含ENV的环境变量
      env:
        - name: ENVVERSION     # 定义环境变量名称
          valueFrom:           # 值来自...
            configMapKeyRef:   # ConfigMap键的引用
              key: version     # ConfigMap的key
              name: simple-cm  # ConfigMap的名称
        - name: ENVRELEASE
          valueFrom:
            configMapKeyRef:
              key: release
              name: simple-cm

创建该Pod,并查看日志:

可以看到,值已经成功从ConfigMap里去到了。

如果要引用某个ConfigMap的所有内容,可以使用下面这种方式。定义一个Pod配置(simple-cm-pod-all.uyml):

apiVersion: v1
kind: Pod
metadata:
  name: simple-cm-pod-all
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "env"]
      envFrom:
        - configMapRef:
            name: simple-cm

创建该Pod,并查看日志:

通过Volume挂载的方式

创建一个Pod配置(file-cm-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: file-cm-pod
spec:
  containers:
    - name: tomcat
      image: tomcat
      ports:
        - containerPort: 8080
      volumeMounts:
        - mountPath: /configs # 容器挂载目录为/configs
          name: config-vm # 引用下面定义的这个Volume
  volumes:
    - name: config-vm   # volume名称为config-vm
      configMap:        # 通过ConfigMap获取
        name: file-cm   # 引用名称为file-cm的ConfigMap
        items:
          - key: serverXml # ConfigMap里配置的key
            path: server.xml # 值使用server.xml文件进行挂载
          - key: serverProperties
            path: server.properties

创建该Pod,并进入到容器内部观察/configs目录下文件内容:

可以看到名称为file-cm的ConfigMap内容已经成功挂载到了tomcat容器内部。

如果在引用ConfigMap时不指定items,则使用volumeMount方式在容器内的目录下为每个item都生成一个文件名为key的文件。

在Pod对ConfigMap进行挂载(volumeMount)操作时,在容器内部只能挂载为“目录”,无法挂载为“文件”。在挂载到容器内部后,在目录下将包含ConfigMap定义的每个item,如果在该目录下原来还有其他文件,则容器内的该目录将被挂载的ConfigMap覆盖

Downward API

Downward API用于将Pod相关信息注入到容器内部,主要有环境变量Volume挂载两种方式。

环境变量

创建一个Pod配置(dapi-pod.yml):

apiVersion: v1
kind: Pod
metadata:
  name: dapi-pod
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "env | grep MY_POD"]
      env:
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name # 通过downward api获取当前pod名称
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace  # 通过downward api获取当前pod namespace
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP  # 通过downward api获取当前pod IP

创建该Pod,并查看日志:

通过环境变量的方式还可以将容器的requests和limits信息注入到容器的环境变量中,创建一个Pod配置(dapi-pod-container-vars.yml):

apiVersion: v1
kind: Pod
metadata:
  name: dapi-pod-container-vars
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c"]
      args:
        - while true;do
            echo -en '\n';
            printenv MY_CPU_REQUEST MY_CPU_LIMIT;
            printenv MY_MEM_REQUEST MY_MEM_LIMIT;
            sleep 3600;
          done;
      resources:
        requests:
          memory: "32Mi"
          cpu: "125m"
        limits:
          memory: "64Mi"
          cpu: "250m"
      env:
        - name: MY_CPU_REQUEST
          valueFrom:
            resourceFieldRef:
              resource: requests.cpu
              containerName: busybox
        - name: MY_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu
              containerName: busybox
        - name: MY_MEM_REQUEST
          valueFrom:
            resourceFieldRef:
              resource: request.memory
              containerName: busybox
        - name: MY_MEM_LIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
              containerName: busybox

创建该Pod并观察日志:

通过Volume挂载

我们可以通过Downward API将Pod的Label,Annotation等信息挂载到容器内部文件中,新建一个Pod配置(dapi-pod-volumes.yml):

apiVersion: v1
kind: Pod
metadata:
  name: dapi-pod-volume
  labels:
    tier: frontend
    release: canary
    environment: dev
  annotations:
    builder: mrbird
    blog: https://mrbird.cc
spec:
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "sleep 36000"]
      volumeMounts:
        - mountPath: /podinfo # 挂载路径
          name: pod-info
  volumes:
    - name: pod-info
      downwardAPI: # 通过downward api获取pod labels和annations信息
        items:
          - path: "labels" # 挂载文件名称
            fieldRef:
              fieldPath: metadata.labels # 挂载内容
          - path: "annotation"
            fieldRef:
              fieldPath: metadata.annotations

创建该Pod,并进入到容器/podinfo目录观察结果:

Pod生命周期

阶段描述
PendingPod 已被 Kubernetes 接受,但尚未创建一个或多个容器镜像。这包括被调度之前的时间以及通过网络下载镜像所花费的时间,执行需要一段时间。
RunningPod 已经被绑定到了一个节点,所有容器已被创建。至少一个容器正在运行,或者正在启动或重新启动。
Succeeded所有容器成功终止,也不会重启。
Failed所有容器终止,至少有一个容器以失败方式终止。也就是说,这个容器要么已非 0 状态退出,要么被系统终止。
Unknown由于一些原因,Pod 的状态无法获取,通常是与 Pod 通信时出错导致的。

三种重启策略:

  1. Always:当容器失效时,由kubelet自动重启该容器;
  2. OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器;
  3. Never:不论容器运行状态如何,kubelet都不会重启该容器。

结合Pod的状态和重启策略,以下为一些常见的状态转换场景:

Pod包含的容器数Pod当前的状态发生事件Pod的结果状态
RestarPolicy=AlwaysRestartPolicy=OnFailureRestartPolicy=Never
包含1个容器Running容器成功退出RunningSucceededSucceeded
包含1个容器Running容器失败退出RunningRunningFailed
包含两个容器Running1个容器失败退出RunningRunningRunning
包含两个容器Running容器被OOM杀掉RunningRunningFailed

Python运算符


2019年7月22日 17:07:54   1,623 次浏览
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
#定义一个由很多内容的列表
infos = ["陈浩东", "虞舜", "公孙策", "王不二", "林雨", "韩寒"]
name = "韩寒"
start = time.time()
for i in infos:
    if name == i:
        print("数据查找到了")
        end = time.time()
        print(end - start)
# 程序运行结果
# 数据查找到了
# 韩寒
# 0.0004734992980957031
#上面的这种查找效率很低,需要循环迭代多次比较耗时,python提供了内部的运算符in(在之内) Not in (不在之内)
start = time.time()
if name in infos:
    print("数据可以查找到")
    end = time.time()
    print(end - start)
else:
    print("没有匹配的信息")
# 程序运行结果
# 数据可以查找到
# 0.00026488304138183594

如果有大量数据存在判断的时候一定要使用in运算符,这样可以保证程序的执行性能,效果也较为显著

自动化测试之UI Recorder问题整理


2019年5月09日 10:25:35   2,461 次浏览

问题1: nodejs安装在c盘,我想把uirecorder安装在d盘命令怎么修改?

例如

npm config set prefix "D:\nodejs\node_global"

npm config set cache "D:\nodejs\node_cache"

问题2:配置了浏览器,但是在webdriver界面上为什么只显示了IE 8??

检查: DB 有没有数据,wd_nodes 表,wd_browsers表

因为之前是只上报了IE浏览器,后面新加的chrome 和FireFox,把这两个表的数据wd_browsers、wd_nodes, 这个节点关联的数据清空一下,然后它会重新上报,就能看到

问题3:我录制的时候浏览器不是最大化录制的,后来点击了最大化,在进行回归测试的时候那一步失败了,请问录制的时候必须要全屏吗?

录制的时候,终端提示要输入分辨率,然后固定窗口录制就可以了,手动点击最大化录制过程是没监听到的。

问题4:日历菜单我录制的时候捕获不到,添加悬停回测还是无法捕获

触发事件是 click 还是 hover?在触发事件后加延迟试试?日历控件的DOM节点加载应该需要一点时间。

问题5:运行多次uirecorder,生成的报告会覆盖上一次的报告,怎么解决?

运行完之后,copy报告保存就可以了。

问题6:生成的报告怎么只关注失败用例情况?

目前只能通过写脚本的方式去解决, uirecorder官方后期会进行一个优化将报告生成json文件,可以更方便脚本去调用。

Python列表操作函数


2019年4月30日 18:32:00   1,650 次浏览

列表可以进行多个数据的存储,同时python中的列表设计非常到位,它可以实现内容动态扩充,可以进行后期数据的删除,这些就需要通过Python提供的列表操作函数来实现了。
对于Python语言而言,开发者肯定要记住一些常用的函数,同事对于一些不常用的函数需要自己进行文档的查看

列表操作函数


append(data)    在列表最后追加新内容
clear()         清除列表数据
copy()          列表拷贝
count()         统计某一个数据在列表中的出现次数
extend(列表)    为一个列表追加另外一个列表
index(data)     从列表查询某个值第一次出现的位置
insert(index,data)  想列表中指定索引位置追加新数据
pop(index)      从列表弹出并删除一个数据
remove(data)    从列表删除数据
reverse()       列表数据反转
sort()          列表数据排序

#空的列表信息

infos = []
print("初始化信息: %d" % len(infos))
infos.append("风哥")
infos.insert(0,"www.guji.work")
print("数据追加后的列表长度: %d, 列表内容: %s" % (len(infos),infos))


程序运行结果:

初始化信息: 0
数据追加后的列表长度: 2, 列表内容: ['www.guji.work', '风哥']

使用append()函数是在列表的最后进行追加的,而insert()是在指定的索引位置上进行添加,添加之后其它的数据向后移动。建议使用append()函数进行内容的追加,这样可以保证数据的顺序。

在进行列表数据追加的时候,还可以追加一个新的列表内容,这个就称为列表的扩充。

#空的列表信息

infos = []
print("初始化列表长度: %d, 地址: %d " % (len(infos),id(infos)))
infos.append("风哥")
infos.extend(["夏丹"])
print("数据追加后的列表长度:%d, 地址: %d, 列表内容:%s " % (len(infos),id(infos),infos))


程序运行结果:

数据追加后的列表长度:2, 地址: 140331673610632, 列表内容:['风哥', '夏丹']

在进行列表数据扩充的操作里面的确是在一个内存空间里面完成的所有功能。列表操作的时候还提供一个列表的拷贝支持,直接使用copy()函数就可以使用当前列表的内容创建新的列表

msg = infos.copy()
print("infos列表的保存地址编号: %d, 列表内容: %s" % (id(infos),infos))
print("msg列表的保存地址编号:%d, 列表内容:%s" %(id(msg),msg))


程序运行结果:

infos列表的保存地址编号: 140200931293704, 列表内容: ['风哥', 'www.guji.work', 'www.baidu.com']
msg列表的保存地址编号:140200931293576, 列表内容:['风哥', 'www.guji.work', 'www.baidu.com']

此时创建了两个不同的内存空间,同时两个空间的内容是完全相同的。

列表除了可以进行内容的扩充之外,也可以执行删除操作,列表里面提供有上传函数的支持,remove()在进行删除的时候是根据内容进行的数据删除

remove()操作需要根据内容删除,并且没有返回值。在使用remove()函数删除的时候内容存在可以删除,不存在就会抛出”ValueError: list.remove(x): x not in list”异常信息,所以如果要使用这个函数操作之前一定要使用in进行判断

infos = ["风哥", "www.guji.work"]
print("数据删除前的列表内容:%s" %(infos))
print("执行数据删除remove()函数 %s" % infos.remove("风哥"))
print("数据删除后的列表内容: %s" % (infos))


程序执行结果:

数据删除前的列表内容:['风哥', 'www.guji.work']
执行数据删除remove()函数 None
数据删除后的列表内容: ['www.guji.work']

如果不知道内容要进行数据的删除,最简单的原始的python支持可以采用del关键字实现内容删除,而且使用del删除的时候只需要知道列表数据的索引即可实现

infos = ["风哥", "www.guji.work"]
print("数据删除前的列表内容:%s" %(infos))
del infos[1]
print("数据删除后的列表内容: %s" % (infos))


程序执行结果为:

数据删除前的列表内容:['风哥', 'www.guji.work']
数据删除后的列表内容: ['风哥']

使用del关键字可以实现索引的删除,但是无法知道被删除了哪些数据,在整个的删除操作里面,最方便的删除是根据索引删除,而后可以告诉用户哪些数据被删除了,这样的功能就是pop函数

infos = ["风哥", "www.guji.work"]
print("数据删除前的列表内容:%s" %(infos))
print("执行数据删除pop()函数: %s " % infos.pop(1))
print("数据删除后的列表内容: %s" % (infos))


程序执行结果:

数据删除前的列表内容:['风哥', 'www.guji.work']
执行数据删除pop()函数: www.guji.work
数据删除后的列表内容: ['风哥']

使用pop()函数表示的是一个弹出的形式,从列表里面根据索引弹出,弹出的同时也就表示内容的删除了。但是需要清楚一个问题,每当列表之中弹出一个数据之后实际上就都会发生索引变更。

infos = ["风哥", "www.guji.work"]
print("数据删除前的列表内容:%s" %(infos))
print("执行数据删除pop()函数: %s " % infos.pop(1))
print("执行数据删除pop()函数: %s " % infos.pop(1))
print("数据删除后的列表内容: %s" % (infos))


程序执行结果:

Traceback (most recent call last):
 File "/tmp/pycharm_project_549/数据分片/列表操作函数.py", line 50, in <module>
   print("执行数据删除pop()函数: %s " % infos.pop(1))
IndexError: pop index out of range




示例二:可以弹0,到最后列表内容为空

infos = ["风哥", "www.guji.work"]
print("数据删除前的列表内容:%s" %(infos))
print("执行数据删除pop()函数: %s " % infos.pop(0))
print("执行数据删除pop()函数: %s " % infos.pop(0))
print("数据删除后的列表内容: %s" % (infos))


程序执行结果:

数据删除前的列表内容:['风哥', 'www.guji.work']
执行数据删除pop()函数: 风哥
执行数据删除pop()函数: www.guji.work
数据删除后的列表内容: []

知识点:

列表中的数据是保存顺序的,列表中所有数据的内容采用的是FIFO(先进先出)默认的顺序操作的,利用列表中的append()和pop()函数就可以方便的实现这样的先进先出的功能。

infos = []
for item in range(10):
   infos.append("风哥 - %d" % item)
print("列表初始化内容: %s" %infos)
print("列表数据弹出处理:")

for item in range(len(infos)):
   print("列表数据弹出: %s" % infos.pop(0))


# 索引会改变,但是从头开始弹出





程序执行结果:

列表初始化内容: ['风哥 - 0', '风哥 - 1', '风哥 - 2', '风哥 - 3', '风哥 - 4', '风哥 - 5', '风哥 - 6', '风哥 - 7', '风哥 - 8', '风哥 - 9']
列表数据弹出处理:
列表数据弹出: 风哥 - 0
列表数据弹出: 风哥 - 1
列表数据弹出: 风哥 - 2
列表数据弹出: 风哥 - 3
列表数据弹出: 风哥 - 4
列表数据弹出: 风哥 - 5
列表数据弹出: 风哥 - 6
列表数据弹出: 风哥 - 7
列表数据弹出: 风哥 - 8
列表数据弹出: 风哥 - 9

 

阿里云线上java应用cpu100%问题处理


2019年3月26日 18:43:00   1,895 次浏览

最近这两天一直收到监控告警提示,告警周期都很短也就持续几分钟所以就忽略了。后面再次出现告警的时候总觉得不正常,于是开始排查原因。

从监控中可以看出cpu使用率一直在居高不下的状态,最高使用率达到93%。

简单分析下可能出问题的地方,分为5个方向:

  1. 系统本身代码问题
  2. 内部下游系统的问题导致的雪崩效应
  3. 上游系统调用量突增
  4. http请求第三方的问题
  5. 机器本身的问题

 

查看日志,没有发现什么明显的错误日志,初步排除代码逻辑处理错误。只发现有个job在处理zip压缩包?接下来继续分析

2019-03-26 18:06:22 |-INFO  [pool-554-thread-8] in  c.f.trip.abc.runable.ElongPriceRefresh - updateEnd!
2019-03-26 18:06:25 |-INFO  [pool-554-thread-4] in  c.f.trip.abc.runable.ElongPriceRefresh - updateEnd!
2019-03-26 18:06:26 |-INFO  [pool-554-thread-3] in  c.f.trip.abc.runable.ElongPriceRefresh - updateEnd!
2019-03-26 18:06:26 |-INFO  [scheduler_Worker-5] in  com.xxx.trip.abc.job.ElongRateUpdateJob - 总线程数:12;
2019-03-26 18:06:41 |-INFO  [scheduler_Worker-5] in  com.xxx.trip.abc.job.ElongRateUpdateJob - 总线程数:12;
2019-03-26 18:06:48 |-INFO  [pool-554-thread-7] in  c.f.trip.abc.runable.ElongPriceRefresh - updateEnd!
2019-03-26 18:06:56 |-INFO  [scheduler_Worker-5] in  com.xxx.trip.abc.job.ElongRateUpdateJob - 总线程数:11;
2019-03-26 18:07:00 |-INFO  [scheduler_Worker-1] in  com.xxx.trip.abc.job.AuditCancelJob - audit cancel job start...
2019-03-26 18:07:00 |-INFO  [scheduler_Worker-7] in  c.f.trip.abc.job.AirPortTransferSmsJob - start AirPortTransferSmsJob ...
2019-03-26 18:07:00 |-INFO  [scheduler_Worker-7] in  c.f.trip.abc.job.AirPortTransferSmsJob - end AirPortTransferSmsJob ...
2019-03-26 18:07:00 |-INFO  [scheduler_Worker-3] in  c.f.t.s.train.abc.impl.TrainOrderServiceImpl - tip orders is empty
2019-03-26 18:07:03 |-INFO  [scheduler_Worker-1] in  com.xxx.trip.abc.job.AuditCancelJob - audit cancel job end!
2019-03-26 18:07:11 |-INFO  [scheduler_Worker-5] in  com.xxx.trip.abc.job.ElongRateUpdateJob - 总线程数:11;
2019-03-26 18:07:20 |-INFO  [pool-554-thread-9] in  c.f.trip.abc.runable.ElongPriceRefresh - updateEnd!
2019-03-26 18:07:26 |-INFO  [scheduler_Worker-5] in  com.xxx.trip.abc.job.ElongRateUpdateJob - 总线程数:10;
2019-03-26 18:07:28 |-INFO  [scheduler_Worker-9] in  com.xxx.trip.abc.job.QiantaoMinpriceJob - try again count :2,url = http://www.xxxx.com:7001/CF100296__hotelPrice_-1183552046.zip

上top工具,好家伙进程PID28204 cpu飙到168,下一步就是定位java线程拿出jstatck来分析。

查看线程top -Hp 28204 ,这里一共跑了600个线程,找到下面几个最高的线程,28210,28775,29475,29473,29468

将线程id转换为16进制 printf “%x\n”  线程ID

使用jstatck查看线程堆栈信息

jstack命令打印线程堆栈信息,命令格式:jstack pid |grep  -A 10 tid

难道是这个线程影响cpu上升的吗? 赶紧联系开发人员一起查看。

开发说这个流式读取写入缓冲区,文件大小500M,左右,读取会比较慢,40分钟左右读完。每次任务跑完一次至少一个半小时

既然开发这样说了我表示怀疑的态度去看了一下配置文件,这分明是10分钟跑一次啊。让开发给个合适的值,修改后重启服务

cpu现在使用时60%,在观察几天看看。