kvm 基础镜像与增量镜像

KVM虚拟机的基本镜像和增量镜像

1、概述

实验目的:通过一个基础镜像(node.img),里面把各个虚拟机都需要的环境都搭建好,然后基于这个镜像建立起一个个增量镜像,每个增量镜像对应一个虚拟机,虚拟机对镜像中所有的改变都记录在增量镜像里面,基础镜像始终保持不变。
功能:节省磁盘空间,快速复制虚拟机。
环境:
基本镜像文件:node.img 虚拟机ID:node
增量镜像文件:node4.img 虚拟机ID:node4
要求:以基本镜像文件node.omg为基础,创建一个镜像文件node4.img,以此创建一个虚拟机机node4,虚拟机node4的改变将存储于node4.img中。

2、创建增量镜像文件

[root@target kvm_node]#qemu-img create -b node.img -f qcow2 node4.img
[root@target kvm_node]# qemu-img info node4.img 
image: node4.img
file format: qcow2
virtual size: 20G (21495808000 bytes)
disk size: 33M
cluster_size: 65536
backing file: node.img (actual path: node.img)

注:该实验只是针对qcow2格式的镜像文件,未测试raw格式的镜像文件是否可行。

3、创建虚拟机node4的XML配置文件

[root@target kvm_node]# cp /etc/libvirt/qemu/node.xml /etc/libvirt/qemu/node4.xml
[root@target kvm_node]# vim /etc/libvirt/qemu/node4.xml 
<domain type='kvm'>
  <name>node4</name>                                  #node4的虚拟机名,须修改,否则与基本虚拟机冲突
  <uuid>4b7e91eb-6521-c2c6-cc64-c1ba72707fe4</uuid>   #node4的UUID,必须修改,否则与基本虚拟机冲突
  <memory>524288</memory>
  <currentMemory>524288</currentMemory>
  <vcpu cpuset='0-1'>2</vcpu>
  <os>
    <type arch='x86_64' machine='rhel5.4.0'>hvm</type>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
    <pae/>
  </features>
  <clock offset='localtime'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>restart</on_crash>
  <devices>
    <emulator>/usr/libexec/qemu-kvm</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/virhost/kvm_node/node4.img'/>    #将原指向/virhost/kvm_node/node.img改为node4.img
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
    </disk>
    <interface type='bridge'>
      <mac address='54:52:00:69:d5:f4'/>             #修改网卡MAC,防止冲突
      <source bridge='br0'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
    <interface type='bridge'>
      <mac address='54:52:00:69:d5:e4'/>            #修改网卡MAC,防止冲突
      <source bridge='br0'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </interface>
    <serial type='pty'>
      <target port='0'/>
    </serial>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <input type='mouse' bus='ps2'/>
    <graphics type='vnc' port='5904' autoport='no' listen='0.0.0.0' passwd='xiaobai'>
      <listen type='address' address='0.0.0.0'/>
    </graphics>
    <video>
      <model type='cirrus' vram='9216' heads='1'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
    <memballoon model='virtio'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </memballoon>
  </devices>
</domain>

4、根据xml配置定义虚拟机node4

[root@target kvm_node]#virsh define /etc/libvirt/qemu/node4.xml
[root@target kvm_node]#virsh start node4  

5、测试

[root@target kvm_node]# du -h node.img

6.3G node.img

[root@target kvm_node]# du -h node4.img

33M node4.img

[root@node4 ~]# dd if=/dev/zero of=test bs=1M count=200   #在虚拟机node4上增量200M大小文件
200+0 records in
200+0 records out
209715200 bytes (210 MB) copied, 1.00361 seconds, 209 MB/s
[root@target kvm_node]# du -h node.img                    #基本镜像文件node.img大小未变

6.3G node.img

[root@target kvm_node]# du -h node.img                    #增量镜像文件node4.img增加200M了
234M    node4.img

Kubernetes(k8s) Pod 弹性伸缩详解与使用

Kubernetes HPA(Horizontal Pod Autoscaling)Pod水平自动伸缩,通过此功能,只需简单的配置,集群便可以利用监控指标(cpu使用率等)自动的扩容或者缩容服务中Pod数量,当业务需求增加时,系统将为您无缝地自动增加适量容器 ,提高系统稳定性。本文将详细讲解HPA的核心设计原理和基于Hepaster的使用方法。

1. HPA概览

未分类
HPA在kubernetes中被设计为一个controller,可以简单的使用kubectl autoscale命令来创建。HPA Controller默认30秒轮询一次,查询指定的resource中(Deployment,RC)的资源使用率,并且与创建时设定的值和指标做对比,从而实现自动伸缩的功能。

  • 当你创建了HPA后,HPA会从Heapster或者用户自定义的RESTClient获取定义的资源中每一个pod利用率或原始值(取决于指定的目标类型)的平均值,然后和HPA中定义的指标进行对比,同时计算出需要伸缩的具体值并进行操作。

当Pod没有设置request时,HPA不会工作。

  • 目前,HPA可以从两种取到获取数据:

  • Heapster(稳定版本,仅支持CPU使用率,在使用腾讯云容器服务时,需要手动安装)。

  • 自定义的监控(alpha版本,不推荐用于生产环境) 。

  • 当需要从自定义的监控中获取数据时,只能设置绝对值,无法设置使用率。

  • 现在只支持Replication Controller, Deployment or Replica Set的扩缩容。

2. 自动伸缩算法

  • HPA Controller会通过调整副本数量使得CPU使用率尽量向期望值靠近,而且不是完全相等.另外,官方考虑到自动扩展的决策可能需要一段时间才会生效:例如当pod所需要的CPU负荷过大,从而在创建一个新pod的过程中,系统的CPU使用量可能会同样在有一个攀升的过程。所以,在每一次作出决策后的一段时间内,将不再进行扩展决策。对于扩容而言,这个时间段为3分钟,缩容为5分钟。

  • HPA Controller中有一个tolerance(容忍力)的概念,它允许一定范围内的使用量的不稳定,现在默认为0.1,这也是出于维护系统稳定性的考虑。例如,设定HPA调度策略为cpu使用率高于50%触发扩容,那么只有当使用率大于55%或者小于45%才会触发伸缩活动,HPA会尽力把Pod的使用率控制在这个范围之间。

  • 具体的每次扩容或者缩容的多少Pod的算法为:

        Ceil(前采集到的使用率 / 用户自定义的使用率) * Pod数量)
  • 每次最大扩容pod数量不会超过当前副本数量的2倍

3. HPA YAML文件详解

下面是一个标准的基于heapster的HPA YAML文件,同时也补充了关键字段的含义

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  creationTimestamp: 2017-06-29T08:04:08Z
  name: nginxtest
  namespace: default
  resourceVersion: "951016361"
  selfLink: /apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/nginxtest
  uid: 86febb63-5ca1-11e7-aaef-5254004e79a3
spec:
  maxReplicas: 5 //资源最大副本数
  minReplicas: 1 //资源最小副本数
  scaleTargetRef:
    apiVersion: extensions/v1beta1
    kind: Deployment //需要伸缩的资源类型
    name: nginxtest  //需要伸缩的资源名称
  targetCPUUtilizationPercentage: 50 //触发伸缩的cpu使用率
status:
  currentCPUUtilizationPercentage: 48 //当前资源下pod的cpu使用率
  currentReplicas: 1 //当前的副本数
  desiredReplicas: 2 //期望的副本数
  lastScaleTime: 2017-07-03T06:32:19Z

4. 如何使用

  • 在上文的介绍中我们知道,HPA Controller有两种途径获取监控数据:Heapster和自定义监控,由于自定义监控一直处于alpha阶段,所以本文这次主要介绍在腾讯云容器服务中使用基于Heapster的HPA方法。

  • 腾讯云容器服务没有默认安装Heapster,所以如果需要使用HPA需要手动安装。

  • 此方法中需要使用kubectl命令操作集群,集群apiservice地址,账号和证书相关信息暂时可以提工单申请,相关功能的产品化方案已经在设计中。

4.1 创建Heapster

创建Heapster ServiceAccount

apiVersion: v1
kind: ServiceAccount
metadata:
  name: heapster
  namespace: kube-system
  labels:
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile

创建Heapster deployment

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: heapster-v1.4.0-beta.0
  namespace: kube-system
  labels:
    k8s-app: heapster
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    version: v1.4.0-beta.0
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: heapster
      version: v1.4.0-beta.0
  template:
    metadata:
      labels:
        k8s-app: heapster
        version: v1.4.0-beta.0
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
    spec:
      containers:
        - image: ccr.ccs.tencentyun.com/library/heapster-amd64:v1.4.0-beta.0
          name: heapster
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8082
              scheme: HTTP
            initialDelaySeconds: 180
            timeoutSeconds: 5
          command:
            - /heapster
            - --source=kubernetes.summary_api:''
        - image: ccr.ccs.tencentyun.com/library/addon-resizer:1.7
          name: heapster-nanny
          resources:
            limits:
              cpu: 50m
              memory: 90Mi
            requests:
              cpu: 50m
              memory: 90Mi
          command:
            - /pod_nanny
            - --cpu=80m
            - --extra-cpu=0.5m
            - --memory=140Mi
            - --extra-memory=4Mi
            - --threshold=5
            - --deployment=heapster-v1.4.0-beta.0
            - --container=heapster
            - --poll-period=300000
            - --estimator=exponential
      serviceAccountName: heapster

创建Heapster Service

kind: Service
apiVersion: v1
metadata: 
  name: heapster
  namespace: kube-system
  labels: 
    kubernetes.io/cluster-service: "true"
    addonmanager.kubernetes.io/mode: Reconcile
    kubernetes.io/name: "Heapster"
spec: 
  ports: 
    - port: 80
      targetPort: 8082
  selector: 
    k8s-app: heapster

保存上述的文件,并使用 kubectl create -f FileName.yaml创建,当创建完成后,可以使用kubectl get 查看

$ kubectl get deployment heapster-v1.4.0-beta.0 -n=kube-system
NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
heapster-v1.4.0-beta.0   1         1         1            1           1m
$ kubectl get svc heapster -n=kube-system
NAME       CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
heapster   172.16.255.119   <none>        80/TCP    4d

4.2 创建服务

创建一个用于测试的服务,可以选择从控制台创建,实例数量设置为1
未分类

4.3 创建HPA

现在,我们要创建一个HPA,可以使用如下命令

$ kubectl autoscale deployment nginxtest --cpu-percent=10 --min=1 --max=10
deployment "nginxtest" autoscaled
$ kubectl get hpa                                                         
NAME        REFERENCE              TARGET    CURRENT   MINPODS   MAXPODS   AGE
nginxtest   Deployment/nginxtest   10%       0%        1         10        13s

此命令创建了一个关联资源nginxtest的HPA,最小的pod副本数为1,最大为10。HPA会根据设定的cpu使用率(10%)动态的增加或者减少pod数量,此地方用于测试,所以设定的伸缩阈值会比较小。

4.4 测试

4.4.1 增大负载

我们来创建一个busybox,并且循环访问上面创建的服务。

$ kubectl run -i --tty load-generator --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # while true; do wget -q -O- http://172.16.255.60:4000; done

下图可以看到,HPA已经开始工作。

$ kubectl get hpa
NAME        REFERENCE              TARGET    CURRENT   MINPODS   MAXPODS   AGE
nginxtest   Deployment/nginxtest   10%       29%        1         10        27m

同时我们查看相关资源nginxtest的副本数量,副本数量已经从原来的1变成了3。

$ kubectl get deployment nginxtest
NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginxtest   3         3         3            3           4d

同时再次查看HPA,由于副本数量的增加,使用率也保持在了10%左右。

$ kubectl get hpa
NAME        REFERENCE              TARGET    CURRENT   MINPODS   MAXPODS   AGE
nginxtest   Deployment/nginxtest   10%       9%        1         10        35m

4.4.2 减小负载

我们关掉刚才的busbox并等待一段时间。

$ kubectl get hpa     
NAME        REFERENCE              TARGET    CURRENT   MINPODS   MAXPODS   AGE
nginxtest   Deployment/nginxtest   10%       0%        1         10        48m
$ kubectl get deployment nginxtest
NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginxtest   1         1         1            1           4d

可以看到副本数量已经由3变为1。

5. 总结

本文主要介绍了HPA的相关原理和使用方法,此功能可以能对服务的容器数量做自动伸缩,对于服务的稳定性是一个很好的提升。但是当前稳定版本中只有cpu使用率这一个指标,是一个很大的弊端。我们会继续关注社区HPA自定义监控指标的特性,待功能稳定后,会持续输出相关文档。

kubernetes(k8s)运行StatefulSet实例

目标

  在你的环境中创建一个PV
  创建一个MySQl的Deployment
  在集群中以DNS名称的方式,将MySQL暴露给其他的pod

开始之前

  你需要一个Kubernetes集群,一个可以连接到集群的kubectl命令行工具。如果你没有集群,你可以使用Minikube来创建。
  我们会创建一个PV(PersistentVolume)用于数据存储。点击这里来查看PV支持的类型,该指导会使用GCEPersistentDisk来演示,但其实任何的PV类型都可以正常工作。GCEPersistentDisk只能在Google Compute Engine(GCE)上工作。

在你的环境中创建磁盘

  在Google Compute Engine,运行:

gcloud compute disks create --size=20GB mysql-disk

  然后创建一个PV,指向刚刚创建的mysql-disk。下面是一个创建PV的配置文件,指向上面提到的GCE磁盘:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  gcePersistentDisk:
    pdName: mysql-disk
    fsType: ext4

  注意pdName: mysql-disk这一行匹配上面GCE环境创建磁盘的名称。如果要在其他环境中创建PV,可以查看Persistent Volumes来获取详细信息。
  创建PV:

kubectl create -f https://k8s.io/docs/tasks/run-application/gce-volume.yaml

部署MySQL

  你可以通过Kubernetes Deployment的方式来创建一个有状态服务,然后使用PVC(PersistentVolumeClaim)来连接已经存在的PV。比如,下面的YAML文件描述了一个运行MySQL并使用PVC的Deployment。文件定义了一个mount到/var/lib/mysql的卷,并创建了一个需要20G卷大小的PVC。
  注意:密码定义在YAML配置文件中,这是不安全的。查看Kubernetes Secrets获取更安全的方案。

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
    - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mysql
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
          # Use secret in real usage
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

  1. 部署YAML文件中的内容。

kubectl create -f https://k8s.io/docs/tasks/run-application/mysql-deployment.yaml

  2. 显示Deployment的信息。

kubectl describe deployment mysql

 Name:                 mysql
 Namespace:            default
 CreationTimestamp:    Tue, 01 Nov 2016 11:18:45 -0700
 Labels:               app=mysql
 Selector:             app=mysql
 Replicas:             1 updated | 1 total | 0 available | 1 unavailable
 StrategyType:         Recreate
 MinReadySeconds:      0
 OldReplicaSets:       <none>
 NewReplicaSet:        mysql-63082529 (1/1 replicas created)
 Events:
   FirstSeen    LastSeen    Count    From                SubobjectPath    Type        Reason            Message
   ---------    --------    -----    ----                -------------    --------    ------            -------
   33s          33s         1        {deployment-controller }             Normal      ScalingReplicaSet Scaled up replica set mysql-63082529 to 1

  3. 显示Deployment创建的pod。

kubectl get pods -l app=mysql

 NAME                   READY     STATUS    RESTARTS   AGE
 mysql-63082529-z3ki   1/1       Running   0          m

  4. 检查PV。

 kubectl describe pv mysql-pv

 Name:            mysql-pv
 Labels:          <none>
 Status:          Bound
 Claim:           default/mysql-pv-claim
 Reclaim Policy:  Retain
 Access Modes:    RWO
 Capacity:        20Gi
 Message:    
 Source:
     Type:        GCEPersistentDisk (a Persistent Disk resource in Google Compute Engine)
     PDName:      mysql-disk
     FSType:      ext4
     Partition:   0
     ReadOnly:    false
 No events.

  5. 检查PVC。

 kubectl describe pvc mysql-pv-claim

 Name:         mysql-pv-claim
 Namespace:    default
 Status:       Bound
 Volume:       mysql-pv
 Labels:       <none>
 Capacity:     20Gi
 Access Modes: RWO
 No events.

访问MySQL实例

  前面的YAML文件创建了一个服务,允许集群的其他Pod可以访问数据库。服务选项clusterIP:None使得服务的DNS名直接解析为Pod的IP地址。当你的服务只有一个Pod,并且你不打算增加Pod的数量时,这是一种最佳的使用方式。
  运行一个Mysql客户端来连接Mysql服务:

kubectl run -it --rm --image=mysql:5.6 mysql-client -- mysql -h <pod-ip> -ppassword

  上面的命令在集群中创建了一个新的Pod,该Pod运行了一个mysql客户端,连接着上面服务的Mysql Server。如果它连接成功,也就说明了这个有状态的MySQL数据库成功启动和运行了。

Waiting for pod default/mysql-client-274442439-zyp6i to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.

mysql>

更新

  更新Deployment的镜像或者其他部分,同样可以照例使用kubectl apply命令来完成。以下是使用有状态应用时需要注意的地方:

  不要扩容该应用。该应用只针对单例应用。下面的PV只能映射给一个Pod。对于集群的有状态应用,请查看StatefulSet文档。
  在Deployment的YAML配置文档中使用strategy: type: Recreate。它会告诉Kubernetes不要使用rolling update。因为Rolling update不会工作,因此不会有多个Pod同时运行。策略Recreate会在使用更新配置创建一个新的Pod时删除之前的Pod。

删除Deployment

  通过名称来删除Deployment对象:

kubectl delete deployment,svc mysql
kubectl delete pvc mysql-pv-claim
kubectl delete pv mysql-pv

  另外,如果你使用的是GCE disk,还需要删除对应的disk:

gcloud compute disks delete mysql-disk

k8s的flannel安装报错Failed to create SubnetManager: error retrieving pod spec解决

花了一个上午来追踪问题,k8s都反复新建了十多次,docker都重启了几次。(一次显示不有获取磁盘空间,重启docker,清空存储解决)

在用kubeadm安装容器化的几个组件时,flannel组件死活不能启动,报如下问题:

Failed to create SubnetManager: error retrieving pod spec for ‘kube-system/kube-flannel-ds-xxx’: the server does not allow access to the requested resource.

在如下Url找到解决办法:

http://blog.csdn.net/ximenghappy/article/details/70157361

明天搞DNS和节点加入….

================================================

Kubernetes一共提供五种网络组件,可以根据自己的需要选择。我使用的Flannel网络,此处1.5.5和1.6.1也是不一样的,1.6.1加了RBAC。需要执行一下两个命令:

kubectl create -f https://github.com/coreos/flannel/raw/master/Documentation/kube-flannel-rbac.yml

clusterrole “flannel” configured
clusterrolebinding “flannel” configured

kubectl create -f  https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

serviceaccount “flannel” created
configmap “kube-flannel-cfg” created
daemonset “kube-flannel-ds” created

  解决此问题参考:
https://github.com/kubernetes/kubernetes/issues/44029
https://github.com/kubernetes/kubeadm/issues/212#issuecomment-290908868

kube-flannel-rbac.yaml文件内容:

# Create the clusterrole and clusterrolebinding:
# $ kubectl create -f kube-flannel-rbac.yml
# Create the pod using the same namespace used by the flannel serviceaccount:
# $ kubectl create --namespace kube-system -f kube-flannel.yml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
rules:
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes/status
    verbs:
      - patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-system

kube-flannel.yaml内容:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flannel
  namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "type": "flannel",
      "delegate": {
        "isDefaultGateway": true
      }
    }
  net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: kube-flannel-ds
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      hostNetwork: true
      nodeSelector:
        beta.kubernetes.io/arch: amd64
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel-amd64:v0.7.1
        command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
        securityContext:
          privileged: true
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      - name: install-cni
        image: quay.io/coreos/flannel-amd64:v0.7.1
        command: [ "/bin/sh", "-c", "set -e -x; cp -f /etc/kube-flannel/cni-conf.json /etc/cni/net.d/10-flannel.conf; while true; do sleep 3600; done" ]
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg

未分类

未分类

docker运行golang应用

搭建 mysql 环境

mysql.app

docker run --net=host -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=letchat -d  hub.c.163.com/library/mysql:latest

1. 通过容器编译并运行当前目录下的 golang程序(依赖需要先下载到当前目录 vendor中)

gobuild.app

docker run --net=bridge --rm -v $PWD:/go/src/app -w /go/src/app hub.c.163.com/library/golang:latest  go build -v

2. 使用 Docker构建镜像,在此镜像上运行一个容器,使用该容器运行 golang程序

Dockerfile

FROM hub.c.163.com/library/golang:latest

WORKDIR /go/src/app
COPY . .

#RUN go-wrapper download   # "go get -d -v ./..."
RUN go-wrapper install    # "go install -v ./..."

CMD ["sh","-c","/go/bin/app"]

gorun.app

# build image  app
docker build -t app:latest .

# set env
export GIN_MODE=release

# make the container
docker run --net=host -it -d app:latest

2 种方式选1个。第一个是直接在本地生成一个 app 程序,可以直接 ./app 运行。第二个是在容器内部运行 app程序,但是网络端口使用宿主机的端口,相当于直接在本机上运行。

docker部署node应用

环境:centos7

docker的安装以及一些设置

安装docker

     yum install docker

启动docker服务

     systemctl start docker.service

设置开机启动

     systemctl enable docker.service

pull下nodejs镜像

说是docker hub 慢,就找到了一个国内的镜像去下载

     docker pull hub.c.163.com/nce2/nodejs:0.12.2

未分类

查看镜像

     docker images

未分类

创建node应用

然后在工作目录下创建package.json文件

     vi package.json

写入内容
未分类

创建server.js,也就是我们node应用的主文件

     vi server.js

写点内容
未分类

创建Dockerfile文件

在项目代码更目录创建Dockerfile文件

     vi Dockerfile

写入以下内容
未分类
下面简单解释下Dockerfile文件

FROM hub.c.163.com/nce2/nodejs:0.12.2

FROM是构建镜像的基础镜像源,后面的一串是我们刚才pull下来的那个Image,如果本地没有Docker,会自己pull镜像的。

#Create app directory
RUN mkdir -p /home/Service
WORKDIR /home/Service

第一句RUN用于在Image里创建一个文件夹,将会用于保存我们的代码。
第二句WORKDIR是将我们创建的文件夹作为工作目录。

# Bundle app Source
COPY . /home/Service
RUN npm install

第一句的COPY是把本机当前目录下的所有文件拷贝到Image的 /home/Service目录下。
第二句的RUN是用npm 安装我们的node应用中的所有依赖包。

EXPOSE 8888

我们的node应用监听的是8888端口,我们可以把这个端口暴露给主机。这样我们就可以从外网访问我们的应用了。

CMD ["npm" , "start"]

用来启动我们的node应用。

构建Image

在我们的Dockerfile所在的目录下执行构建命令

docker build -t mynodeapp .

别忘了最后那个点,不久我们会看到成功构建的信息。
未分类

然后我们去查看一下我们构建的镜像

docker images

未分类

运行构建的Image

docker run -d -p 8888:8888 50550c

-d表示容器会在后台运行,-p表示端口映射,把本机的8888端口映射到container的8888端口,这样外网就可以通过本机访问我们的应用,
后面的50550c是我们Image的ID,没写后面的是因为根据这几个就能定位出来了。

竟然报错了,也不能访问8888端口。想了一下,应该是镜像的问题。
就跑去dochub找了官方镜像,修改Dockerfile里的基础镜像

FROM node:4-onbuild

然后再次构建我们的应用

docker build -t nodeapp .

然后开始从官网下载镜像,确实特别慢..等了好久才下载完1个。貌似需要下载9个文件..
在漫长的等待过程中。。。
未分类

算了,放弃。。找找国内镜像试试吧~~

然后去daocloud注册了帐号,在这拉镜像吧

未分类
很快,镜像pull下来了。然后查看下现在的镜像。

未分类
现在我们去改下Dockerfile的基础镜像吧。

FROM daocloud.io/library/node

现在我们再来构建下镜像试试

docker build -t nodeapp .

看到了构建成功的信息,接着查看一下imges

然后启动:

docker run -d -p 8888:8888 7b784

果然出现了问题… 启动不成功,最后才知道,镜像和Dockerfile都是对应的,不对应就可能会出现上面的所有问题。(切记)

最近还是选择了官网的镜像,等待了40分钟左右,全部pull下来了,开始干活~~

修改Dockerfile文件如下:

FROM node:4-onbuild
# replace this with your application's default port
EXPOSE 8888

重新构建:

docker build -t my-node-apps .

未分类

启动docker镜像:

docker run -d -p 8888:8888 8b

然后查看下container

docker ps

未分类

然后用浏览器访问下8888端口:

未分类
完美!!!!!!!!!这次终于成功了~

然后去删除没用的images和container…

若要删除所有的image, 使用命令:

docker rmi  $( docker  images -q )

删除container

docker rm id

总结

因为镜像的事情走了不少弯路,浪费了很多的时间,所以最好还是去官网下需要的东西,然后看官方给的文档,英文的也要尽力去看。
今后还要去做学习一些images的管理,container的管理,以及应用日志处理相关的东西。

使用lua模拟tail -n命令读取最后n行

最近需要使用lua读取文件的最后n行数据,但不想调用linux中的tail命令来获取,于是使用纯lua来实现。

实现思路

  1. 把文件指针偏移距离文件尾x个字节
  2. 读取x个字节数据
  3. 在这x个字节数据中查找换行符n,如果找到n个换行符,把文件指针偏移到第n个换行符的位置,输出全部内容
  4. 如果找不到足够的换行符,继续把文件指针在当前位置向文件头方向偏移x个字节
  5. 返回2步骤循环,直到找到足够换行符或到文件头

lua代码

tail.lua

#!/usr/bin/lua

if arg[1] == "-n" then
    tail_lines = arg[2]
    filepath = arg[3]
else
    tail_lines = 10
    filepath = arg[1]
end

-- 一次读取512字节数据
read_byte_once = 512
offset = 0
fp = io.open(filepath,"r")
if fp == nil then
    print("open file "..filepath.." failed.")
    os.exit(0)
end
line_num = 0
while true do
    -- 每次偏移read_byte_once字节
    offset = offset - read_byte_once
    -- 以文件尾为基准偏移offset
    if fp:seek("end",offset) == nil then
        -- 偏移超出文件头后将出错,这时如果是第一次读取的话,直接将文件指针偏移到头部,否则跳出循环输出所有内容
        if offset + read_byte_once == 0 then
            fp:seek("set")
        else
            break
        end
    end
    data = fp:read(read_byte_once)
    -- 倒转数据,方便使用find方法来从尾读取换行符
    data = data:reverse()
    index = 1
    while true do
        -- 查找换行符
        start = data:find("n",index, true)
        if start == nil then
            break
        end
        -- 找到换行符累加
        line_num = line_num + 1
        -- 找到足够换行符
        if tail_lines + 1 == line_num then
            -- 偏移文件符指针到第line_num个换行符处
            fp:seek("end",offset+read_byte_once-start+1)
            io.write(fp:read("*all"))
            fp:close()
            os.exit(0)
        end
        index = start + 1
    end
end

-- 找不到足够的行,就输出全部
fp:seek("set")
io.write(fp:read("*all"))
fp:close() 

用法

读取centos.log最后10行

./tail.lua centos.log

读取centos.log最后20行

./tail.lua -n 20 centos.log

OpenResty(Nginx Lua)获取Nginx Worker CPU使用率

在上文我们介绍了三种获取进程cpu使用率的方法,本文介绍使用openresty来获取所有nginx worker的cpu使用率,然后提供一个接口来输出cpu使用率。由于收集cpu使用率需要获取两次,两次之间需要等待一些时间,为了保证此接口的性能,决定不采用接口实时统计,采用后台定时统计,然后接口查询其数据就行。
所有步骤思路为:

  1. 在init_worker阶段获取所有的worker pid
  2. 在init_worker阶段启动一个定时器来统计所有nginx worker cpu使用率并存储到共享字典
  3. 接口查询共享字典中的结果返回给客户端

int_worker定时统计cpu使用率

http {
    [...]
    lua_shared_dict dict 10m;
    init_worker_by_lua_block {
        -- 获取所有worker pid到字典
        local worker_pid = ngx.worker.pid()
        local worker_id = ngx.worker.id()
        ngx.shared.dict:set(worker_id,worker_pid)

        -- 统计cpu使用率函数
        local function count_cpu_usage(premature)
            -- 首次获取cpu时间
            local worker_cpu_total1 = 0
            local cpu_total1 = 0

            local worker_count = ngx.worker.count()

            for i=0, worker_count - 1 do    
                local worker_pid = ngx.shared.dict:get(i)
                local fp = io.open("/proc/"..worker_pid.."/stat","r")
                local data = fp:read("*all")
                fp:close()
                local res, err = ngx.re.match(data, "(.*? ){13}(.*?) (.*?) ", "jio")
                worker_cpu = res[2] + res[3]
                worker_cpu_total1 = worker_cpu_total1 + worker_cpu
            end

            local fp = io.open("/proc/stat","r")
            local cpu_line = fp:read()
            fp:close()
            local iterator, err = ngx.re.gmatch(cpu_line,"(\d+)")
            while true do
                local m, err = iterator()
                if not m then
                    break
                end

                cpu_total1 = cpu_total1 + m[0]
            end

            -- 第二次获取cpu时间
            ngx.sleep(0.5)
            local worker_cpu_total2 = 0
            local cpu_total2 = 0

            for i=0, worker_count -1 do    
                local worker_pid = ngx.shared.dict:get(i)
                local fp = io.open("/proc/"..worker_pid.."/stat","r")
                local data = fp:read("*all")
                fp:close()
                local res, err = ngx.re.match(data, "(.*? ){13}(.*?) (.*?) ", "jio")
                worker_cpu = res[2] + res[3]
                worker_cpu_total2 = worker_cpu_total2 + worker_cpu
            end

            local fp = io.open("/proc/stat","r")
            local cpu_line = fp:read()
            fp:close()
            local iterator, err = ngx.re.gmatch(cpu_line,"(\d+)")
            while true do
                local m, err = iterator()
                if not m then
                    break
                end

                cpu_total2 = cpu_total2 + m[0]
            end

            -- 获取cpu核心数
            local cpu_core = 0
            local fp = io.open("/proc/cpuinfo")
            local data = fp:read("*all")
            fp:close()
            local iterator, err = ngx.re.gmatch(data, "processor","jio")
            while true do
                local m, err = iterator()
                if not m then
                    break
                end
                cpu_core = cpu_core + 1
            end

            -- 计算出cpu时间
            local nginx_workers_cpu_time = ((worker_cpu_total2 - worker_cpu_total1) / (cpu_total2 - cpu_total1)) * 100*cpu_core
            nginx_workers_cpu_time = string.format("%d", nginx_workers_cpu_time)
            ngx.shared.dict:set("nginx_workers_cpu_time",nginx_workers_cpu_time)
        end


        -- 定时任务
        local function count_cpu_usage_timed_job()
            -- 定义间隔执行时间
            local delay = 2
            local count
            count = function(premature)
                if not premature then
                    local ok, err = pcall(count_cpu_usage, premature)
                    if not ok then
                        log(ERR, "count cpu usage error:",err)
                    end    
                    local ok, err = ngx.timer.at(delay, count)
                    if not ok then
                        return
                    end
                end
            end
            local ok, err = ngx.timer.at(delay, count)
            if not ok then
                return
            end
        end

        -- 执行定时任务
        count_cpu_usage_timed_job()
    }
    [...]
}

定义获取cpu使用率的接口

location /cpu {
    content_by_lua_block {
        local nginx_workers_cpu_time = ngx.shared.dict:get(nginx_workers_cpu_time)
        ngx.header.content_type = 'text/plain'
        ngx.say("nginx_workers_cpu_time")
    }
}

使用代理服务器解决升级WordPress慢的问题

更新WordPress版本有两种方法,在后台点击自动升级或者手动下载新的版本替换旧的。为了方便,一般选择前者自动升级。不过由于自动升级使用的是国外的服务器downloads.wordpress.org,在国内自动升级wordpress的话会非常慢甚至超时失败。不过wordpress提供了可以设置代理了方法,本人在国外服务器架设了一台代理服务器,可以直接拿来用,由于代理服务器限制了只能代理wordpress.org域名,使用完后请注释掉代码,以免影响wordpress使用。

解决方法

打开wp-config.php,在底部增加如下代码:

  1. define(‘WP_PROXY_HOST’, ‘us.webres.wang’);
  2. define(‘WP_PROXY_PORT’, ‘31281’);

保存之后登录wordpress后台,切换到更新,点击升级按钮开始更新。
更新完成后请注释掉刚才加的代码,即在前面添加//,如:

  1. //define(‘WP_PROXY_HOST’, ‘us.webres.wang’);
  2. //define(‘WP_PROXY_PORT’, ‘31281’);

下次需要升级时再删除注释。
未分类

ezhttp配置yum或apt仓库镜像

重新配置yum或apt仓库镜像是为了获取更快的软件安装速度。比如机器默认配置的仓库源为国外的服务器,而我们在国内的服务器使用这个源就会非常的慢,非常地浪费时间。使用ezhttp可以在centos,debian,ubuntu自动配置仓库镜像。在开始之前,请先查看ezhttp介绍

1.Configure_apt_yum_repository

进入Some Useful Tools -》 Configure_apt_yum_repository菜单项

2.选择设置的apt或yum镜像

目前可选的镜像为:
1) mirrors.ustc.edu.cn(recommended)
2) mirrors.sohu.com
3) mirrors.aliyun.com
4) mirrors.163.com
输入对应的数字回车开始设置。如图:
未分类