在Kubernetes的3个node上部署redis cluster

目的

redis clustor 需要6台服务器才能正常运⾏,由于种种原因,开发或者某些特别的需求,只能在3台服务器上运⾏redis clustor。在不使用哨兵模式情况下,而使⽤最新的clustor模式运行redis。

本文仅作为redis部署方式的研究及理解

准备工作

制作redis docker.latest镜像其中包含以下组件:

  1. redis-cli

  2. ruby

  3. redis-trib

打包到镜像上传到阿里镜像服务器中cluster-redis:latest

创建集群操作

3台服务器上各自运行两个redis容器

未分类

使用以下编写好的redis-cluster部署文件,可在一台部署出两个不同端口,不同角⾊的redis容器。

redis-cluster.yaml

apiVersion: extensions/v1beta1
 kind: Deployment
 metadata:
 name: redis-blue
 labels:
 app: redis
 member: redis-blue
 spec:
 replicas: 3
 template:
 metadata:
 labels:
 app: redis
 member: redis-blue
 spec:
 hostNetwork: true
 containers:
 - name: redis
 image: registry.cn-hangzhou.aliyuncs.com/wise2c/cluster-redis:latest
 command: ["/bin/sh", "-c"]
 args: ["echo 'dir /tmp/data' >> /root/redis.conf && /usr/local/bin/redis-server /root/redis.conf"]
 #- /usr/local/bin/redis-server
 #- /root/redis.conf
 ports:
 - name: redis-port
 containerPort: 6379
 - name: cluster-port
 containerPort: 16379
 volumeMounts:
 - mountPath: /tmp/data
 name: data
 volumes:
 - name: data
 hostPath:
 path: /tmp/redis-blue
 ---
 apiVersion: extensions/v1beta1
 kind: Deployment
 metadata:
 name: redis-green
 labels:
 app: redis
 member: redis-green
 spec:
 replicas: 3
 template:
 metadata:
 labels:
 app: redis
 member: redis-green
 spec:
 hostNetwork: true
 containers:
 - name: redis
 image: registry.cn-hangzhou.aliyuncs.com/wise2c/cluster-redis:latest
 command: ["/bin/sh", "-c"]
 args: ["sed -i 's/6379/6380/g' /root/redis.conf && echo 'dir /tmp/data' >> /root/redis.conf && /usr/local/ports:
 - name: redis-port
 containerPort: 6380
 - name: cluster-port
 containerPort: 16380
 volumeMounts:
 - mountPath: /tmp/data
 name: data
 volumes:
 - name: data
 hostPath:
 path: /tmp/redis-green

kubectl create -f redis-cluster.yaml

执行以下脚本,创建出整个redis集群。redis会自动分配哈希槽,使得master与其对应的slave不会出现在同一台服务器上。

create_cluster.sh

#!/usr/bin/env bash
 redis_count=`kubectl get pod -o wide -l app=redis | grep Running | wc -l`
 #echo "redis_count:"$redis_count
 if [ $redis_count -ne 6 ]; then
 echo "the running redis count: ${redis_count} is error"
 exit 1
 fi
 redis_blue_ips=`kubectl get pod -o wide -l app=redis -l member=redis-blue | awk 'NR>1{printf $6":6379 "}'
 redis_green_ips=`kubectl get pod -o wide -l app=redis -l member=redis-green | awk 'NR>1{printf $6":6380 "}'
 redis_ips=$redis_blue_ips" "$redis_green_ips
 echo "redis_ips:"$redis_ips
 redis_blue_name=`kubectl get pod -o wide -l app=redis -l member=redis-blue | grep Running | awk '{printf $1" "}'
 #echo $redis_ips | awk -F' ' '{for( i=1;i<NF; i++ ) print $i}' `` kubectl create -f redis-cluster.yaml bash create_cluster.sh

关掉其中一台机器 由于master和slave 不在同一台机器上,当我们直接关掉其中⼀一台vm,比如vm2

未分类

这时vm3上,由redis cluster自动恢复vm3slave2(6380) —> master2(6380) 提升为master,集群工作正常,业务不中断。

恢复关掉的的机器

当我们重新启动vm2, 这时候集群的状态:

未分类

这时集群工作正常,此时vm3有2个master。如果我们关掉vm3,会让集群中2个master同时下线,导致集群无法自动恢复。

重点:执行以下脚本,提升slave2到master2,恢复集群的自动修复功能。

failover.sh

#!/usr/bin/env bash
 redis_blue_name=`kubectl get pod -o wide -l app=redis -l member=redis-blue | grep Running | awk '{printf $1":6379 redis_names=`kubectl get pod -o wide -l app=redis -l member=redis-blue | grep Running | awk '{printf $1","}'
 redis_array=${redis_names//,/ }
 for redis_name in $redis_array
 do
 kubectl exec -it ${redis_name} -- redis-cli cluster failover
 done

bash failover.sh

集群自动恢复,变成下面的状态。

未分类

集群工作正常,业务不中断。

作者后话

以上的操作,目的是让各台虚拟上不出现有2个master。当其中一台虚拟机出现当机,集群就不能正常工作.。如果是3台master和3台slave分别在不同的机器刚好错开,redis cluster能自动恢复.。最好的解决方案,依然是有6台虚拟机、3台master、3台slave的redis cluster。

To do

我们可以进⼀一步将

  • create-redis.sh

  • failover.sh

  • kubectl

制作为镜像, k8s deployment运行此镜像。实现:

  • 创建集群

  • 恢复集群的自动修复功能(任意关掉其中一台服务器,业务不中断。每过一秒,检查集群中是否有slave需要提升权限,集群都能正常工作.业务不中断)。

在Kubernetes上使用Sateful Set部署RabbitMQ集群

1. RabbitMQ的基础知识

在正式开始部署工作之前,我们先来复习一下RabbitMQ的一些基础知识。

RabbitMQ内建的集群功能可以实现其高可用,允许消费者和生产者在RabbitMQ节点崩溃的情况下继续工作,同时可以通过添加更多的节点来提高消息处理的吞吐量。

RabbitMQ内部主要包含以下四种Meta Data:

  • vhost meta data:为RabbitMQ内部的Queue, Exchange, Binding提供命名空间级别的隔离

  • exchange meta data:记录Exchange的名称、类型、属性等

  • binding meta data:表示Routing Key和Queue之间的绑定关系,即描述如何将消息路由到队列Queue中

  • queue meta data: 记录队列的名称及其属性

单个节点的RabbitMQ会将这些meta data保存到内存中,同时对于那些属性为持久化的信息,例如durable的Exchange、Queue等持久化到硬盘上,持久化到硬盘上的Exchange和Queue可以在RabbitMQ节点重启后被重新创建。

当以集群形式部署RabbitMQ的多个节点时,RabbitMQ集群需要新的meta data来保存集群的信息。RabbitMQ集群有以下两种模式:

  • 普通模式:在这种模式下,对于集群中rabbit1和rabbit2两个节点,一个消息只会存在于其中某个节点上的Queue上。rabbit1和rabbit2这两个节点仅仅是拥有相同的meta data,即队列的结构和属性。当consumer连接rabbi2消费rabbit1上的消息时,RabbitMQ会在这两个节点上进行消息传输,将rabbit1上的消息传输到rabbit2上。在该模式下consumer和producer应该尽量连接每个节点,在多个节点建立物理队列,这样也起到了线性扩展的作用。但是在这种模式下要考虑一种情况,某个节点挂掉时其上面还有没有被消费的消息:如果队列和消息都做了持久化,只有该节点恢复时,消息才可以继续被消费;如果队列和消息没有持久化的话,就会丢失消息。

  • 镜像模式:就是把队列做成镜像队列,存在于多个节点上,在该模式下,消息会在节点的镜像队列间做同步,这样可以实现RabbitMQ高可用,但会降低系统性能,特别是镜像队列数量较多,大量消息进入和同步时会占用集群内部大量带宽。因此镜像模式使用于对可靠性要求比较高的场景。

接下来看一下镜像队列的声明,可以通过rabbitmqctl命令或在RabbitMQ Management WebUI中通过创建Policies的方式来声明镜像队列。例如:

rabbitmqctl set_policy ha-all "^ha." '{"ha-mode":"all"}'

上面这个命令配置了策略,所有名称以ha.开始的队列,都会在集群的所有节点上成为镜像队列。这里使用的ha模式是all,另外还有exactly, nodes两种模式,分别可以指定具体的镜像节点数量,镜像节点名称,可以参考Highly Available (Mirrored) Queues,这里不再展开。

通过上面对RabbitMQ基础知识的一个简单的回顾,在使用RabbitMQ需要考虑一下几点:

  • Queue和Message是否要做持久化

  • 在使用RabbitMQ的集群时是否要使用镜像队列

2. 构建RabbitMQ Docker镜像

RabbitMQ提供了一个Autocluster插件,可以自动创建RabbitMQ集群。下面我们将基于RabbitMQ的官方docker镜像,添加这个autocluster插件,构建我们自己的Rabbit镜像,以便在Kubernetes上使用这个镜像。

首选需要从这里下载autocluster和rabbitmq_aws插件,我这里下载的是0.8.0的最新版本。

mkdir -p rabbitmq/plugins
cd rabbitmq/plugins
wget https://github.com/rabbitmq/rabbitmq-autocluster/releases/download/0.8.0/autocluster-0.8.0.ez
wget https://github.com/rabbitmq/rabbitmq-autocluster/releases/download/0.8.0/rabbitmq_aws-0.8.0.ez
cd ..

我的Dockerfile的内容如下:

FROM rabbitmq:3.6.11-management-alpine

MAINTAINER frognew

RUN apk update && apk add ca-certificates && 
    apk add tzdata && 
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && 
    echo "Asia/Shanghai" > /etc/timezone


ADD plugins/*.ez /opt/rabbitmq/plugins/
RUN rabbitmq-plugins enable --offline autocluster
  • 这里选择是rabbitmq:3.6.11-management-alpine作为基础镜像

  • 添加了autocluster插件

下面构建这个镜像,并将其推送到我们的私有仓库:

docker build -t harbor.frognew.com/library/rabbitmq:3.6.11 .
docker push harbor.frognew.com/library/rabbitmq

3. 以StatefulSet部署RabbitMQ

接下来将以以StatefulSet部署RabbitMQ集群,我们继续使用Ceph的块存储RBD作为存储卷,将RabbitMQ的数据保存在Ceph RBD中。 这就需要对我们的Kubernetes和Ceph集群做一些准备工作,需要在Ceph中创建专门给Kubernetes使用的存储池,同时配置Kubernetes的Node节点访问Ceph,并在Kubernetes上创建StorageClass,关于这些内容不再展开,可以参考之前写的在Kubernetes上使用Sateful Set部署Redis中2.1~2.3的内容。

前面在构建RabbitMQ的Docker镜像时,我们添加了autocluster插件,这个插件基于很多种backend做服务发现自动将发现的RabbitMQ节点添加到RabbitMQ集群中,autocluster当前支持如下几种backend:

  • AWS EC2 tags

  • AWS Autoscaling Groups

  • Kubernetes

  • DNS A records

  • Consul

  • etcd

Kubernetes赫然在列,实际上当使用Kubernetes作为rabbitmq-autocluster的backend时,autocluster会通过访问Kubernetes的API Server获取RabbitMQ服务的endpoints,这样就能拿到Kubernete集群中的RabbitMQ的Pod的信息,从而可以将它们添加到RabbitMQ的集群中去。 这里也就是说要在autocluster实际上是在RabbitMQ Pod中要访问Kubernetes的APIServer。

可是然后呢?因为已经对Kubernetes的API Server启用了TLS认证,同时也为API Server启用了RBAC,要想从Pod中访问API Server需要借助Kubernetes的Service Account。 Service Account是Kubernetes Pod中的程序用于访问Kubernetes API的Account(账号),它为Pod中的程序提供访问Kubernetes API的身份标识。下面我们创建rabbitmq Pod的ServiceAccount,并针对Kubernetes的endpoint资源做授权,创建相关的role和rolebinding。

先说明一下,假设我们的部署是在dev这个namespace下的。创建如下的rabbitmq.rbac.yaml文件:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rabbitmq
  namespace: dev
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rabbitmq
  namespace: dev
rules:
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rabbitmq
  namespace: dev
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: dev
subjects:
- kind: ServiceAccount
  name: rabbitmq
  namespace: dev

在Kubernetes上创建rabbitmq这个ServiceAccount以及相关的role和rolebinding:

kubectl create -f rabbitmq.rbac.yaml

下面创建rabbitmq.statefulset.yaml文件:

---
apiVersion: v1
kind: Service
metadata:
  name: rabbitmq-management
  namespace: dev
  labels:
    app: rabbitmq
spec:
  ports:
  - port: 15672
    name: http
    nodePort: 32001
  - port: 5672
    name: amqp
    nodePort: 32002
  selector:
    app: rabbitmq
  type: NodePort
---
apiVersion: v1
kind: Service
metadata:
  name: rabbitmq
  namespace: dev
  labels:
    app: rabbitmq
spec:
  clusterIP: None
  ports:
  - port: 5672
    name: amqp
  selector:
    app: rabbitmq
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: rabbitmq
  namespace: dev
spec:
  serviceName: rabbitmq
  replicas: 3
  template:
    metadata:
      labels:
        app: rabbitmq
    spec:
      serviceAccountName: rabbitmq
      imagePullSecrets: 
        - name: regsecret
      containers:
      - name: rabbitmq
        image: harbor.frognew.com/library/rabbitmq:3.6.11
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            memory: "256Mi"
            cpu: "150m"
          limits:
            memory: "512Mi"
            cpu: "250m"
        ports:
        - containerPort: 5672
          name: amqp
        env:
          - name: RABBITMQ_DEFAULT_USER
            value: rabbituser
          - name: RABBITMQ_DEFAULT_PASS
            valueFrom:
              secretKeyRef:
                name: devsecret
                key: rabbitDefaultPass
          - name: RABBITMQ_ERLANG_COOKIE
            valueFrom:
              secretKeyRef:
                name: devsecret
                key: rabbitmqErlangCookie
          - name: MY_POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: K8S_SERVICE_NAME
            value: "rabbitmq"
          - name: RABBITMQ_USE_LONGNAME
            value: "true"
          - name: RABBITMQ_NODENAME
            value: "rabbit@$(MY_POD_NAME).$(K8S_SERVICE_NAME)"
          - name: RABBITMQ_NODE_TYPE
            value: disc
          - name: AUTOCLUSTER_TYPE
            value: "k8s"
          - name: AUTOCLUSTER_DELAY
            value: "10"
          - name: AUTOCLUSTER_CLEANUP
            value: "true"
          - name: CLEANUP_WARN_ONLY
            value: "false"
          - name: K8S_ADDRESS_TYPE
            value: "hostname"
          - name: K8S_HOSTNAME_SUFFIX
            value: ".$(K8S_SERVICE_NAME)"
        volumeMounts:
        - name: rabbitmq-volume
          mountPath: /var/lib/rabbitmq
  volumeClaimTemplates:
  - metadata:
      name: rabbitmq-volume
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi
  • 通过环境变量RABBITMQ_USE_LONGNAME, RABBITMQ_NODENAME, AUTOCLUSTER_TYPE, AUTOCLUSTER_DELAY, K8S_ADDRESS_TYPE, AUTOCLUSTER_CLEANUP等环境变量配置了autocluster插件,具体可以参考 RabbitMQ Autocluster中的文档内容

  • 通过RABBITMQ_ERLANG_COOKIE指定了Erlang cookie。RabbitMQ的集群是通过Erlang OTP实现的,而Erlang节点间通信的认证通过Erlang cookie来允许通信,这里从devsecret这个Secret中挂载。关于devsecret这个Secret这里不再给出。

  • 通过RABBITMQ_DEFAULT_USER和RABBITMQ_DEFAULT_PASS指定了RabbitMQ的管理员用户名和密码,也是从devsecret这个Secret中挂载的

  • 通过RABBITMQ_NODE_TYPE设置集群所有节点类型为disc,即为磁盘节点

为了在Kubernetes上运行RabbitMQ集群,必须保证各个RabbitMQ节点之间可以通信,也就是SatefulSet的Pod可以通信。 采用的RabbitMQ节点的命名方式为rabbit@hostdomainname的形式:

[email protected] ([email protected])
[email protected] ([email protected])
[email protected] ([email protected])

可以看出采用的是长节点名的命名方式,因此设置了RABBITMQ_USE_LONGNAME为true。为了保证节点间可以通过访问rabbitmq-0.rabbit, rabbitmq-1.rabbit, rabbitmq-2.rabbit这些域名通信,必须使用Headless Service,上面rabbitmq Service的clusterIP: None这个必须设置。

在Kubernetes上创建Service和StatefulSet:

kubectl create -f rabbitmq.statefulset.yaml

kubectl get statefulset rabbitmq -n dev
NAME       DESIRED   CURRENT   AGE
rabbitmq   3         3         25m

最后可以在RabbitMQ Management中查看RabbitMQ的3个节点已经组成了集群:

未分类

在Kubernetes上使用Sateful Set部署Redis

最近需要在我们的一个Kubernetes集群上部署Redis,因此重新整理,写一下如何在Kubernetes上使用Sateful Set部署Redis。

一、需求和环境

我们的需求是需要部署三节点的Redis主从复制,并部署三个节点的Redis Sentinel实现Redis的高可用。

环境信息如下:

  • Kubernetes 1.6.7集群
  • Ceph 11.2.0集群

Kubernetes的官方examples中已经给出了一个在k8s集群上部署Redis的例子Reliable, Scalable Redis on Kubernetes, 就是基于Redis主从复制+Sentinel实现的,但是这个例子是以无状态服务形式部署的,如果整个k8s集群重启了,Redis的状态就会丢失,因此不能用于生产环境。 但我们可以参考这个例子,以Satefult Set的形式部署。

我们的线上环境主要使用Ceph的块存储RBD作为Kubernetes的存储卷,这里可以将Redis服务的状态保存在Ceph RBD中。

二、Storage Classes和Dynamic Storage Provision

Kubernetes 1.6开始Storage Classes和Dynamic Storage Provision已经是稳定可用的了。 StorageClass是Dynamic Storage Provision的基础,k8s的管理员可以定义底层存储平台抽象。 用户通过在PVC(Persistent Volume Claim)中通过名字引用StorageClass,PV(Persistent Volume)将使用StorageClass来动态创建,这样就节省了集群管理员手动创建PV的时间。

1、在Ceph中创建存储池Pool

我们需要先在Ceph中创建一个k8s集群专用的Ceph Pool,在创建之前我们先看一下当前Ceph集群中的存储池:

ceph osd lspools
0 rbd,1 .rgw.root,2 default.rgw.control,3 default.rgw.data.root,4 default.rgw.gc,5 default.rgw.lc,6 default.rgw.log,7 default.rgw.users.uid,8 default.rgw.users.email,9 default.rgw.users.keys,10 default.rgw.buckets.index,11 default.rgw.buckets.data,

一个Ceph集群可以有多个pool,pool是逻辑上的存储池。不同的pool可以有不一样的数据处理方式,例如replica size, placement groups, crush rules,snapshot等等。 可以看到因为我们这个环境还是用Ceph的RGW作为我们的对象存储,因此除了默认的名称为rbd的pool外,还有很多rgw的pool。

下面创建一个专门给k8s集群专用的pool kube:

ceph osd pool create kube 128
pool 'kube' created

ceph osd lspools
0 rbd,1 .rgw.root,2 default.rgw.control,3 default.rgw.data.root,4 default.rgw.gc,5 default.rgw.lc,6 default.rgw.log,7 default.rgw.users.uid,8 default.rgw.users.email,9 default.rgw.users.keys,10 default.rgw.buckets.index,11 default.rgw.buckets.data,12 kube,
  • 当前这个ceph集群只有3个osd,所以设置pg_num为128,可参考PLACEMENT GROUPS

2、配置k8s Node节点访问Ceph

为了让Kubernetes的Node可以调用rbd,如果Ceph集群和Kubernetes集群不是在相同的机器上,还需要在Kubernetes的Node上安装ceph-common:

yum install -y ceph-common

接下来在Kubernetes上创建ceph-secret,这个Secret将用于Kubernetes集群的StorageClass上。

我们先查看一下ceph集群上的所有用户列表:

ceph auth list

这个命令会列出针对Ceph的每种类型的进程已经创建的不同权限的用户,同时也会列出client.admin用户,这个是Ceph集群的管理员用户。

接下来我们创建一个client.kube用户:

ceph auth get-or-create client.kube
[client.kube]
        key = AQAzcYVZ6sbJLhAA7qCBywM+iPRgAG97FtoXIw==

创建好的client.kube用户用户还没有任何权限,下面给其授权:

ceph auth caps client.kube mon 'allow r' osd 'allow rwx pool=kube'
updated caps for client.kube

查看用户和权限信息:

ceph auth get client.kube
exported keyring for client.kube
[client.kube]
        key = AQAzcYVZ6sbJLhAA7qCBywM+iPRgAG97FtoXIw==
        caps mon = "allow r"
        caps osd = "allow rwx pool=kube"

因为Kubernetes的Secret需要Base64编码,下面将这个keyring转换成Base64编码:

ceph auth get-key client.kube | base64
QVFBemNZVlo2c2JKTGhBQTdxQ0J5d00raVBSZ0FHOTdGdG9YSXc9PQ==

接下来创建Secret,ceph-secret.yaml:

apiVersion: v1
kind: Secret
metadata:
  name: ceph-secret
  namespace: kube-system
type: kubernetes.io/rbd
data:
  key: QVFBemNZVlo2c2JKTGhBQTdxQ0J5d00raVBSZ0FHOTdGdG9YSXc9PQ==
kubectl create -f ceph-secret.yaml
secret "ceph-secret" created

3、在k8s集群创建StorageClass

首先检查我们的集群中是否有默认的StorageClass:

kubectl get storageclass
No resources found.

我们这里使用的k8s集群是使用ansible部署的Kubernetes 1.6 高可用集群,可以看出我们部署的这个集群并没有创建默认的StorageClass。

我们现在集群中创建默认的Storage Class, storege.yaml文件如下:

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: default
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
  labels:
    kubernetes.io/cluster-service: "true"
provisioner: kubernetes.io/rbd
parameters:
    monitors: 192.168.61.3:6789,192.168.61.4:6789,192.168.61.5:6789
    adminId: kube
    adminSecretName: ceph-secret
    adminSecretNamespace: kube-system
    pool: kube
    userId: kube
    userSecretName: ceph-secret-user
  • annotations中storageclass.kubernetes.io/is-default-class: “true”表示这个StorageClass是集群默认的StorageClass

  • provisioner: kubernetes.io/rbd表示这个StorageClass的类型时Ceph RBD

  • parameters配置了这个StorageClass使用的Ceph集群以及RBD的相关参数

  • monitors是逗号分隔的Ceph Mon节点地址

  • adminId指定Ceph client 的ID需要具有能在配置的Ceph RBD Pool中创建镜像的权限。默认值为admin

  • adminSecret:adminId的Secret Name,该Secret的type必须是”kubernetes.io/rbd”,该参数是必须的

  • adminSecretNamespace: adminSecret的namespace,默认为”default”

  • pool: Ceph RBD Pool,默认为”rbd”

  • userId: Ceph client Id,用来映射RBD镜像。

  • userSecretName: userId在映射RBD镜像时所需要的Secret的名称。该Secret要求必须出现在和PVC相同的namespace内,并且type必须是”kubernetes.io/rbd”。该参数是必须的

创建这个默认的StorageClass:

kubectl create -f storage.yaml
storageclass "default" created

kubectl get storageclass
NAME                TYPE
default (default)   kubernetes.io/rbd
  • (default)表示这个名称为default的StorageClass是k8s集群默认的StorageClass

三、构建Redis的Docker镜像

参考 Reliable, Scalable Redis on Kubernetes (https://github.com/kubernetes/kubernetes/tree/master/examples/storage/redis/image) 中的Redis镜像,我们的Redis的Dockerfile定制如下:

FROM harbor.frognew.com/rg/alpine-glibc:0.1

RUN apk add --no-cache redis sed bash

COPY redis-master.conf /redis-master/redis.conf
COPY redis-slave.conf /redis-slave/redis.conf
COPY run.sh /run.sh
RUN chmod u+x /run.sh
CMD [ "/run.sh" ]

ENTRYPOINT [ "bash", "-c" ]
  • alpine-glibc:0.1是我们的基础镜像,在alpine:3.6的基础上增加了glibc,并将时区设置为Asia/Shanghai

参考 Reliable, Scalable Redis on Kubernetes (https://github.com/kubernetes/kubernetes/tree/master/examples/storage/redis/image) 中的run.sh做如下定制,原来的run.sh不支持对redis设置密码,加上从环境变量$REDIS_PASS读取redis密码:

#!/bin/bash

# Copyright 2014 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function launchmaster() {
  if [[ ! -e /redis-master-data ]]; then
    echo "Redis master data doesn't exist, data won't be persistent!"
    mkdir /redis-master-data
  fi
  sed -i "s/%redis-pass%/${REDIS_PASS}/" /redis-master/redis.conf
  redis-server /redis-master/redis.conf --protected-mode no
}

function launchsentinel() {
  while true; do
    master=$(redis-cli -a $REDIS_PASS -h ${REDIS_SENTINEL_SERVICE_HOST} -p ${REDIS_SENTINEL_SERVICE_PORT} --csv SENTINEL get-master-addr-by-name mymaster | tr ',' ' ' | cut -d' ' -f1)
    if [[ -n ${master} ]]; then
      master="${master//"}"
    else
      master=${REDIS_MASTER_SERVICE_HOST}
    fi

    redis-cli -a $REDIS_PASS -h ${master} INFO
    if [[ "$?" == "0" ]]; then
      break
    fi
    echo "Connecting to master failed.  Waiting..."
    sleep 10
  done

  sentinel_conf=sentinel.conf

  echo "sentinel monitor mymaster ${master} 6379 2" > ${sentinel_conf}
  echo "sentinel auth-pass mymaster ${REDIS_PASS}" >> ${sentinel_conf}
  echo "sentinel down-after-milliseconds mymaster 60000" >> ${sentinel_conf}
  echo "sentinel failover-timeout mymaster 180000" >> ${sentinel_conf}
  echo "sentinel parallel-syncs mymaster 1" >> ${sentinel_conf}
  echo "bind 0.0.0.0" >> ${sentinel_conf}

  redis-sentinel ${sentinel_conf} --protected-mode no
}

function launchslave() {
  while true; do
    master=$(redis-cli -a $REDIS_PASS -h ${REDIS_SENTINEL_SERVICE_HOST} -p ${REDIS_SENTINEL_SERVICE_PORT} --csv SENTINEL get-master-addr-by-name mymaster | tr ',' ' ' | cut -d' ' -f1)
    if [[ -n ${master} ]]; then
      master="${master//"}"
    else
      echo "Failed to find master."
      sleep 60
      exit 1
    fi 
    redis-cli -a $REDIS_PASS -h ${master} INFO
    if [[ "$?" == "0" ]]; then
      break
    fi
    echo "Connecting to master failed.  Waiting..."
    sleep 10
  done
  sed -i "s/%master-ip%/${master}/" /redis-slave/redis.conf
  sed -i "s/%master-port%/6379/" /redis-slave/redis.conf
  sed -i "s/%redis-pass%/${REDIS_PASS}/" /redis-slave/redis.conf
  redis-server /redis-slave/redis.conf --protected-mode no
}

if [[ "${MASTER}" == "true" ]]; then
  launchmaster
  exit 0
fi

if [[ "${SENTINEL}" == "true" ]]; then
  launchsentinel
  exit 0
fi

launchslave
  • 这个脚本根据环境变量MASTER, SENTINEL来判断是启动不同类型的redis进程,如果MASTER为true,则启动redis master,否则如果SENTINEL为true则启动redis sentinel,否则启动redis salve

  • 从环境变量REDIS_PASS中读取并设置redis的密码

redis-master.conf的配置文件内容如下:

daemonize no
pidfile /var/run/redis.pid
port 6379
tcp-backlog 511
bind 0.0.0.0
timeout 0
tcp-keepalive 60
loglevel notice
logfile ""
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /redis-master-data
slave-serve-stale-data yes
rename-command FLUSHALL ""
rename-command FLUSHDB ""
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
requirepass %redis-pass%
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes

redis-slave.conf配置文件内容如下:

daemonize no
pidfile /var/run/redis.pid
port 6379
tcp-backlog 511
bind 0.0.0.0
timeout 0
tcp-keepalive 60
loglevel notice
logfile ""
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir "/data"
slaveof %master-ip% %master-port% 
masterauth %redis-pass%
slave-serve-stale-data yes
rename-command FLUSHALL ""
rename-command FLUSHDB ""
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
requirepass %redis-pass%
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes

构建redis镜像并推送到我们的私有仓库:

docker build -t harbor.frognew.com/rg/redis:1.0 .
docker push harbor.frognew.com/rg/rg/redis

四、在Kubernetes集群上部署Redis

假设我们的redis要部署在devops这个namespace下,先在这个namespace下创建ceph-secret-user这个Secret:

apiVersion: v1
kind: Secret
metadata:
  name: ceph-secret-user
  namespace: devops
type: kubernetes.io/rbd
data:
  key: QVFBemNZVlo2c2JKTGhBQTdxQ0J5d00raVBSZ0FHOTdGdG9YSXc9PQ==
kubectl crate -f ceph-secret-user.yaml

1、redis-master.statefulset.yaml

redis-master.statefulset.yaml是redis master的Service和StatefulSet。

apiVersion: v1
kind: Service
metadata:
  name: redis-master
  namespace: devops
  labels:
    name: redis-master
spec:
  ports:
    - port: 6379
  selector:
    redis-master: "true"

---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: redis-master
  namespace: devops
  labels:
    name: redis-master
spec:
  serviceName: redis-master
  replicas: 1
  template:
    metadata:
      labels:
        app: redis-master
        redis-master: "true"
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: redis
        image: harbor.frognew.com/rg/redis:1.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 6379
        env:
        - name: MASTER
          value: "true"
        - name: REDIS_PASS
          valueFrom:
            secretKeyRef:
              name: devopssecret
              key: redisAuthPass
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        volumeMounts:
        - name: redis-master-volume
          mountPath: /redis-master-data
      imagePullSecrets: 
        - name: regsecret
  volumeClaimTemplates:
  - metadata:
      name: redis-master-volume
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi
  • 通过设置环境变量MASTER为true,表明以master形式启动redis,而环境变量REDIS_PASS从devopssecret这个Secret中获取的值,这里略过devopssecret这个Secret的内容

  • volumeClaimTemplates中定义了PVC,因为没有给定storageClassName,所以将使用我们前面创建的默认的StorageClass,会根据PVC动态创建StatefulSet中Pod所需的PV

2、redis-sentinel.statefulset.yaml

redis-sentinel.statefulset.yaml定义了redis-sentinel的Service和StatefulSet:

apiVersion: v1
kind: Service
metadata:
  name: redis-sentinel
  namespace: devops
  labels:
    name: redis-sentinel
spec:
  ports:
    - port: 26379
      targetPort: 26379
  selector:
    redis-sentinel: "true"
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: redis-sentinel
  namespace: devops
spec:
  serviceName: redis-sentinel
  replicas: 3
  template:
    metadata:
      labels:
        redis-sentinel: "true"
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: redis-sentinel
        image: harbor.frognew.com/rg/redis:1.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 26379
          name: redis-sentinel
        env:
          - name: SENTINEL
            value: "true"
          - name: REDIS_PASS
            valueFrom:
              secretKeyRef:
                name: devopssecret
                key: redisAuthPass
      imagePullSecrets: 
        - name: regsecret
  • sentinel的启动逻辑可以查看Docker镜像中的run.sh中launchsentinel()的逻辑

5、redis.statefulset.yaml

redis.statefulset.yaml定义了redis slave的Service和SatefulSet:

apiVersion: v1
kind: Service
metadata:
  name: redis
  namespace: devops
  labels:
    app: redis
spec:
  ports:
    - port: 6379
  clusterIP: None
  selector:
    app: redis
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: redis
  namespace: devops
  labels:
    name: redis
spec:
  serviceName: redis
  replicas: 2
  template:
    metadata:
      labels:
        app: redis
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: redis
        image: harbor.frognew.com/rg/redis:1.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 6379
        env:
        - name: REDIS_PASS
          valueFrom:
            secretKeyRef:
              name: devopssecret
              key: redisAuthPass
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        volumeMounts:
        - name: redis-volume
          mountPath: /data
      imagePullSecrets: 
        - name: regsecret
  volumeClaimTemplates:
  - metadata:
      name: redis-volume
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi

volumeClaimTemplates中定义了PVC,因为没有给定storageClassName,所以将使用我们前面创建的默认的StorageClass,会根据PVC动态创建StatefulSet中Pod所需的PV。

4、以StatefulSet的形式部署Redis

下面实际操作一遍基于StatefulSet的Redis的部署。

先创建redis-master的Service和StatefulSet:

kubectl create -f redis-master.statefulset.yaml
service "redis-master" created
statefulset "redis-master" created

确保这redis master Pod处于running状态:

kubectl get pods -l redis-master="true" -n devops
NAME             READY     STATUS    RESTARTS   AGE
redis-master-0   1/1       Running   0          48s

kubectl get svc -l name="redis-master" -n devops
NAME           CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
redis-master   10.104.132.220   <none>        6379/TCP   1m

下面创建redis-sentinel的Service和StatefulSet:

kubectl create -f redis-sentinel.statefulset.yaml
service "redis-sentinel" created
statefulset "redis-sentinel" created

kubectl get svc -l name="redis-sentinel" -n devops -o wide
NAME             CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE       SELECTOR
redis-sentinel   10.97.4.9    <none>        26379/TCP   16s       redis-sentinel=true

查看StatefulSet确保DESIRED和CURRENT的数量是相同的。

kubectl get statefulset -n devops
NAME             DESIRED   CURRENT   AGE
redis-master     1         1         3m
redis-sentinel   3         3         42s

查看sentinel Pod:

kubectl get pod -l redis-sentinel="true" -n devops
NAME               READY     STATUS    RESTARTS   AGE
redis-sentinel-0   1/1       Running   0          1m
redis-sentinel-1   1/1       Running   0          1m
redis-sentinel-2   1/1       Running   0          1m

下面创建redis slave的Service和StatefulSet:

kubectl create -f redis.statefulset.yaml
service "redis" created
statefulset "redis" created

注意上面的过程中,在创建redis master和slave的stateful set时可能需要一定的时间,因为涉及到PVC, PV, rbd image的创建,耐心等待。

因为redis-master这个StatefulSet的副本数为1,redis slave这个SatefulSet中的副本数为2,所以我们可以看到集群中创建了3个PVC,并创建了3个PV:

kubectl get pvc -n devops
NAME                                 STATUS    VOLUME                                     CAPACITY   ACCESSMODES   STORAGECLASS   AGE
redis-master-volume-redis-master-0   Bound     pvc-fd2c30e3-7b14-11e7-ad4a-1866da8c6175   5Gi        RWO           default        10m
redis-volume-redis-0                 Bound     pvc-65a2ba2c-7b17-11e7-ad4a-1866da8c6175   5Gi        RWO           default        7m
redis-volume-redis-1                 Bound     pvc-6a96951c-7b17-11e7-ad4a-1866da8c6175   5Gi        RWO           default        7m


kubectl get pv -n devops
NAME                                       CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS    CLAIM                                       STORAGECLASS   REASON    AGE
pvc-65a2ba2c-7b17-11e7-ad4a-1866da8c6175   5Gi        RWO           Delete          Bound     devops/redis-volume-redis-0                 default                  10m
pvc-6a96951c-7b17-11e7-ad4a-1866da8c6175   5Gi        RWO           Delete          Bound     devops/redis-volume-redis-1                 default                  7m
pvc-fd2c30e3-7b14-11e7-ad4a-1866da8c6175   5Gi        RWO           Delete          Bound     devops/redis-master-volume-redis-master-0   default                  7m

一定要确认STATUS的状态为Bound,如果不是可以通过kubectl describe pvc -n 查看具体的事件。实际上rbd image的创建是由controller-manager调用rbd命令完成的,所以如果有问题也可以看一下controller-manager的日志。

下面的decribe pv命令详细打印出了这个PV已经使用Ceph RBD Image:

kubectl describe pv pvc-65a2ba2c-7b17-11e7-ad4a-1866da8c6175 -n devops
Name:           pvc-65a2ba2c-7b17-11e7-ad4a-1866da8c6175
Labels:         <none>
Annotations:    pv.kubernetes.io/bound-by-controller=yes
                pv.kubernetes.io/provisioned-by=kubernetes.io/rbd
StorageClass:   default
Status:         Bound
Claim:          devops/redis-volume-redis-0
Reclaim Policy: Delete
Access Modes:   RWO
Capacity:       5Gi
Message:
Source:
    Type:               RBD (a Rados Block Device mount on the host that shares a pod's lifetime)
    CephMonitors:       [192.168.61.3:6789 192.168.61.4:6879 192.168.61.5:6789]
    RBDImage:           kubernetes-dynamic-pvc-856cff45-7a7b-11e7-ac3c-1866da8c2fcd
    FSType:
    RBDPool:            kube
    RadosUser:          kube
    Keyring:            /etc/ceph/keyring
    SecretRef:          &{ceph-secret-user}
    ReadOnly:           false
Events:                 <none>

另外可以在Ceph集群中查看创建的rbd image:

rbd list kube
kubernetes-dynamic-pvc-856cff45-7a7b-11e7-ac3c-1866da8c2fcd
kubernetes-dynamic-pvc-8b1be6fc-7a7b-11e7-ac3c-1866da8c2fcd
kubernetes-dynamic-pvc-cce6429c-7a7a-11e7-ac3c-1866da8c2fcd

rbd info -p kube --image kubernetes-dynamic-pvc-856cff45-7a7b-11e7-ac3c-1866da8c2fcd
rbd image 'kubernetes-dynamic-pvc-856cff45-7a7b-11e7-ac3c-1866da8c2fcd':
        size 5120 MB in 1280 objects
        order 22 (4096 kB objects)
        block_name_prefix: rb.0.3e17b.238e1f29
        format: 1

kubectl get statefulset -n devops
NAME             DESIRED   CURRENT   AGE
redis            2         2         5m
redis-master     1         1         10m
redis-sentinel   3         3         7m

我们重点来看一下redis statefulset和redis service:

kubectl get svc -l app="redis" -n devops
NAME      CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
redis     None         <none>        6379/TCP   7m


kubectl get pod -l app="redis" -n devops
NAME      READY     STATUS    RESTARTS   AGE
redis-0   1/1       Running   0          7m
redis-1   1/1       Running   0          7m

注意redis service的CLUSTER-IP为None,这是由有状态服务的特征决定的。 有状态服务具有以下特征:

  • 要求有稳定的网络身份,即唯一不变的hostname,并保存在DNS中。hostname是由statefulset的名字后边跟随”-序号”组成,这里是redis-1, redis-2。 同时每个Pod的网络身份也是通过Service定义被创建出来了,根据Service的定义,通过ClusterIp:None指定,该Service将在DNS生成一条没有ClusterIP的记录。

  • 要求有持久稳定的存储,通过PVC和PV提供。这里使用了Kubernetes的通过Dynamic Storage Provision特性,PV使用StorageClass来动态创建。

  • redis-0,redis-1这两个是reddis的slave节点。

最后我们来看一下k8s集群中redis节点:

kubectl get statefulset -n devops
NAME             DESIRED   CURRENT   AGE
redis            2         2         8m
redis-master     1         1         12m
redis-sentinel   3         3         10m

我们以StatefulSet的形式部署了1个master, 2个slave, 3个sentinel。当其中master节点发生故障时,sentinel会从剩余redis节点中选举新的master并切换。 3个redis节点的数据都是保存在ceph rbd中。

使用iptables设定特定端口连接数和速度

摘要:限制端口连接数量首先输入命令serviceiptablesstop关闭iptables限制端口并发数很简单,IPTABLES就能搞定了,假设你要限制端口8388的IP最大连接数为5,两句话命令:i…

限制端口连接数量

首先输入命令service iptables stop关闭iptables

限制端口并发数很简单,IPTABLES就能搞定了,假设你要限制端口8388的IP最大连接数为5,两句话命令:

iptables -I INPUT -p tcp --dport 8388 -m connlimit --connlimit-above 15 -j DROP

iptables -I OUTPUT -p tcp --dport 8388 -m connlimit --connlimit-above 15 -j DROP

我再举个例子,比如你想限制从1024-10240的端口

iptables -I INPUT -p tcp --dport 1024:10240 -m connlimit --connlimit-above 15 -j DROP

iptables -I OUTPUT -p tcp --dport 1024:10240 -m connlimit --connlimit-above 15 -j DROP

保存IPTABLES规则即可(service iptables save),其他端口以此类推。

输入命令service iptables start启动

最后用命令查看是否生效

iptables -L -n -v

限制端口速度

首先输入命令service iptables stop关闭iptables

限制端口并发数很简单,IPTABLES就能搞定了,假设你要限制端口5037的最大连接速度为60个包每秒,两句话命令:

iptables -A INPUT -p tcp --sport 5037 -m limit --limit 60/s -j ACCEPT

iptables -A INPUT -p tcp --sport 5037 -j DROP

也就是限制每秒接受60个包,一般来说每个包大小为64—1518字节(Byte)。

限制指定ip的访问速度

原理:每秒对特定端口进行速度控制,比如每秒超过10个的数据包直接DROP,从而限制特定端口的速度

iptables -A FORWARD -m limit -d 208.8.14.53 --limit 700/s --limit-burst 100 -j ACCEPT 

iptables -A FORWARD -d 208.8.14.53 -j DROP

最后说一下如何解决防火墙重启后失败的问题

iptables-save >/etc/sysconfig/iptables

echo 'iptables-restore /etc/sysconfig/iptables' >> /etc/rc.local

chmod +x /etc/rc.d/rc.local

多主机Docker容器的VLAN划分

参考文档:

1、Docker网络的4种模式,pipework/ovs的简单使用等:http://www.infoq.com/cn/articles/docker-network-and-pipework-open-source-explanation-practice

2、Dockerpool全文档:https://yeasy.gitbooks.io/docker_practice/content/index.html

3、Ovs完全使用手册:http://my.oschina.net/tantexian/blog/648965?fromerr=C7Pp6sMs

ovs相对linux自身的brctl工具(yum install -y bridge-utils)功能上丰富许多,如vlan功能与分布式功能。

本文主要验证利用ovs的vlan功能实现跨主机的容器隔离与通信。

一. 前置条件

1. 拓扑图

未分类

2. 环境说明

  • Host1/2为VMware ESXi中的vm host,安装CentOS-7-x86_64-1511系统;

  • 在宿主机Host1/2上各创建两个Container,为Container创建eth1端口;

  • Container的eth1端口连接到ovs网桥,ovs网桥对接container的端口分别划分到vlan10/20;

  • 宿主机Host1/2的网卡ens192分别桥接到各宿主机内部的 ovs网桥;

  • 宿主机Host1/2的网卡ens192需要设置成混杂模式,并且对接的交换机端口设置为trunk端口。

  • 网桥需要安装 bridge-utils 包;

  • 常规情况下,加入 bridge 网络的宿主机网卡自动进入 promiscuous mode ,并进入 forwarding state ( 可以使用 dmesg 查看 ) ;但如果宿主机是 vm ,需要注意调整宿主机的网卡为 promiscuous mode ,调整”伪传输”模式为”接受”,对接虚拟网桥的端口为 trunk 端口。如 VMware ESXi 默认拒绝接受混杂模式下的数据包并且不对数据包打 tag ;伪传输”模式默认为”拒绝”表示出站数据的源 mac 地址不同于 .vmx 文件中的源 mac 地址时, vswitch 会丢弃该出站数据;” mac 地址更改”模式默认为”拒绝”表示 vm 在操作系统层面将网卡的 mac 地址更改为不同于 .vmx 配置文件中的 mac 地址时,丢弃所有入站数据。

3. pipework

#pipework本质是一个shell脚本实现,相对docker自身比较薄弱的网络配置处理方式,其可以方便地对docker网络进行配置。
[root@localhost ~]# cd /usr/local/
[root@localhost local]# git clone https://github.com/jpetazzo/pipework

#可以使用软连接,复制,或增加环境变量的多种方式将pipework设为可执行命令
[root@localhost local]# ln -s /usr/local/pipework/pipework /usr/local/bin/ 

二. 多主机Docker容器的vlan划分

以下操作没有特别说明,都表示在Host1上操作,Host2按照Host1操作微调即可。

1. 启动容器

#在Host1/2下启动容器test1/2/3/4;
#"--net=none"设置容器启动不带网络,由后期自定义,关于容器网络的模式请参考链接文档1
[root@localhost ~]# docker run -itd --net=none --name test1 centoswithssh
[root@localhost ~]# docker run -itd --net=none --name test2 centoswithssh
[root@localhost ~]# docker ps

未分类

2. 为容器配置网络

#用pipework添加ovs0网桥,test1网络划分到vlan10,test2网络划分到vlan20;
#pipework本质是采用shell脚本简化了ovs的操作,pipework及ovs的具体操作请参考链接文档3;
#这里并没有为ovs0网桥设置管理ip,可以根据需要设置,请参考链接1。
[root@localhost ~]# pipework ovs0 test1 192.168.1.11/24 @10
[root@localhost ~]# pipework ovs0 test2 192.168.1.12/24 @20

[root@localhost ~]# ovs-vsctl show
[root@localhost ~]# docker exec -it test1 ifconfig

未分类

未分类

3. 为网桥添加宿主机网卡

#Host1与Host2中的容器需要通信,需要把两边的网络打通,即将宿主机网卡添加到虚拟网桥中
[root@localhost ~]# ovs-vsctl add-port ovs0 ens192
[root@localhost ~]# ovs-vsctl show

4. 验证

[root@localhost ~]# docker exec -it test1 ping 192.168.1.12
[root@localhost ~]# docker exec -it test1 ping 192.168.1.13
[root@localhost ~]# docker exec -it test1 ping 192.168.1.14
[root@localhost ~]# docker exec -it test2 ping 192.168.1.13
[root@localhost ~]# docker exec -it test2 ping 192.168.1.14

预期:

  • test1 ping test2: fail
  • test1 ping test3: success
  • test1 ping test4: fail
  • test2 ping test3: fail
  • test2 ping test4: success

根据验证结果显示,结果符合预期,请见截图:

未分类

未分类

原 运维利器之AWK 蔡佳娃

未分类

功能

一个行文本处理工具,可以逐行处理文件中的数据。

语法

本文中提到的cjw.txt内容大致如下:

Installing fontpackages-filesystem-1.41-1.1.el6.noarch
warning: fontpackages-filesystem-1.41-1.1.el6.noarch: Header V3 RSA/SHA256 Signature, key ID c105b9de: NOKEY
Installing liberation-fonts-common-1.05.1.20090721-5.el6.noarch
Installing xml-common-0.6.3-32.el6.noarch
Installing iso-codes-3.16-2.el6.noarch
Installing setup-2.8.14-20.el6.noarch
Installing filesystem-2.4.30-3.el6.i686
Installing dejavu-fonts-common-2.30-2.el6.noarch
Installing xkeyboard-config-2.6-6.el6.noarch
Installing control-center-filesystem-2.28.1-38.el6.i686
Installing paktype-fonts-common-2.0-8.el6.noarch

语法说明

awk 'pattern + {action}'

desc:
1. 单引号''是为了和shell命令区分开
2. 大括号{}表示一个命令分组
3. pattern是一个过滤器,表示符合pattern的行才进行action处理
4. action是处理动作
5. 使用#作为注释

eg.
#显示cjw.txt中的第三行到第五行
awk 'NR==3,NR==5{print}' cjw.txt

#输出
Installing liberation-fonts-common-1.05.1.20090721-5.el6.noarch
Installing xml-common-0.6.3-32.el6.noarch
Installing iso-codes-3.16-2.el6.noarch

常用命令选项

-F fs fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:

-v var=value 赋值一个用户定义变量,将外部变量传递给awk

-f scripfile 从脚本文件中读取awk命令

-m[fr] val 对val值设置内在限制

pattern说明

pattern参数可以是grep正则中的一个,正则使用/pattern/。

#显示cjw.txt中,正则匹配Install的行
awk '/Install/' cjw.txt

#输出
Installing fontpackages-filesystem-1.41-1.1.el6.noarch
Installing liberation-fonts-common-1.05.1.20090721-5.el6.noarch
Installing xml-common-0.6.3-32.el6.noarch
Installing iso-codes-3.16-2.el6.noarch
...

pattern和action可以只有其一,但不能两者都没有,默认的action是print。

#显示cjw.txt中,长度大于80行的内容
awk 'length($0)>80' cjw.txt
#等价于
awk 'length($0)>80 {print $0}' cjw.txt


#显示cjw.txt中,长度大于80的行号和内容
awk 'length($0)>80 {print NR, $0}' cjw.txt

#输出
2 warning: fontpackages-filesystem-1.41-1.1.el6.noarch: Header V3 RSA/SHA256 Signature, key ID c105b9de: NOKEY

内置变量

未分类

#显示cjw.txt中第3行到第5行的第一列与最后一列
awk 'NR==3,NR==5{print $1,$NF}' cjw.txt

#输出
Installing liberation-fonts-common-1.05.1.20090721-5.el6.noarch
Installing xml-common-0.6.3-32.el6.noarch
Installing iso-codes-3.16-2.el6.noarch


#按定界符"-"分割每行的内容,然后打印输出第一列和第二列
awk 'BEGIN{FS="-"}{print $1,$2}' cjw.txt

#输出
Installing fontpackages filesystem
warning: fontpackages filesystem
Installing liberation fonts
Installing xml common
Installing iso codes
Installing setup 2.8.14

内置函数

未分类

#将文本中的Installing替换为cjw,输出可以替换的行号以及替换后的内容
awk 'gsub("Installing", "cjw"){print NR, $0}' cjw.txt

#输出
1 cjw fontpackages-filesystem-1.41-1.1.el6.noarch
3 cjw liberation-fonts-common-1.05.1.20090721-5.el6.noarch
4 cjw xml-common-0.6.3-32.el6.noarch
5 cjw iso-codes-3.16-2.el6.noarch
...


#输出cjw.txt文本中的包含common的行号及其行内容
awk 'match($0,"common") {print NR, $0}' cjw.txt

#输出
3 Installing liberation-fonts-common-1.05.1.20090721-5.el6.noarch
4 Installing xml-common-0.6.3-32.el6.noarch
8 Installing dejavu-fonts-common-2.30-2.el6.noarch
11 Installing paktype-fonts-common-2.0-8.el6.noarch
...

操作符

支持+、-、*、、、%、++、-、+=等运算操作。支持==、!=、>、~等判断操作。

控制语句

BEGIN和END本质是一个pattern。BEGIN用于awk程序开始开始前,做一些初始化的工作;END用于awk程序结束前,做一些收尾的工作。

流程控制语句与其他语言几乎相同,就不在此赘述。

#使用空格将info进行切割,输出切割后数组的长度以及函数split的返回值
awk 'BEGIN{info="it is a test";lens=split(info,tA," "); print length(tA),lens;}'

#输出
4 4


#控制流的使用
awk 'BEGIN { 
    test=100; 
    if (test>90) { 
        print "very good"; 
    } else if (test>60) { 
        print "good"; } 
    else { 
        print "no pass"; 
    } 
}'

#输出
very good

RHELCentOS 7 下 MySQL 连接数被限制为214个

问题

项目中,由于连接数过多,提示“Too many connections”,需要增加连接数。我在 /etc/my.cnf中修改了

max_connections = 2000

但是, 实际连接数一直被限制在 214

mysql> show variables like "max_connections";
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 214   |
+-----------------+-------+
1 row in set

MySQL max_connections 总是 214 。不能设大了?

环境

  • CentOS 7.1
  • MySQL 5.6.25

思考

如果我设置连接小于214时,比如 200,那么实际连接数就是 200,也就是说,我的配置文件是没有问题的。

查 MySQL 官方文档,里面说了

The maximum number of connections MySQL can support depends on the quality of the thread library on a given platform, the amount of RAM available, how much RAM is used for each connection, the workload from each connection, and the desired response time. Linux or Solaris should be able to support at 500 to 1000 simultaneous connections routinely and as many as 10,000 connections if you have many gigabytes of RAM available and the workload from each is low or the response time target undemanding. Windows is limited to (open tables × 2 + open connections) < 2048 due to the Posix compatibility layer used on that platform.
Increasing open-files-limit may be necessary. Also see Section 2.5, “Installing MySQL on Linux”, for how to raise the operating system limit on how many handles can be used by MySQL.

大概意思是 MySQL 能够支持的最大连接数量受限于操作系统,必要时可以增大 open-files-limit。换言之,连接数与文件打开数有关。

解决

执行

[root@emsc ~]# ulimit -n
1024

可知,操作系统最大文件描述符限制为 1024, 在 配置文件中添加

open_files_limit = 65535

实际上也没有生效

更改 MySQL 在 Linux 的最大文件描述符限制,编辑 /usr/lib/systemd/system/mysqld.service 文件,在文件最后添加:

LimitNOFILE=65535
LimitNPROC=65535

保存后,执行下面命令,使配置生效

$ systemctl daemon-reload
$ systemctl restart  mysqld.service

实际连接数到 2000 了,解决

mysql> show variables like "max_connections";
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 2000  |
+-----------------+-------+
1 row in set

优化InnoDB磁盘I/O

如果你遵循数据库设计和调优技术的最佳做法,但由于磁盘I/O活动较大,你的数据库仍然很慢,请考虑这些磁盘I/O优化。如果top工具显示你的CPU使用率百分比低于70%,则你的工作负载可能是磁盘导致的。几种优化方式如下:

增加缓冲池大小

当表数据缓存在InnoDB缓冲池中时,可以通过查询重复访问,而不需要任何磁盘I/O。使用innodb_buffer_pool_size选项指定缓冲池的大小。该内存区域足够重要,通常建议将innodb_buffer_pool_size其配置为系统内存的50%到75%。有关更多信息,请参见第8.12.4.1节“MySQL如何使用内存”。

调整数据刷新方法

在某些版本的GNU/Linux和Unix中,使用Unix fsync()调用( InnoDB默认情况下使用)将文件刷新到磁盘是非常缓慢的。如果数据库写入性能是一个问题,请使用innodb_flush_method参数设置为基准O_DSYNC。

这个参数控制着InnoDB数据文件及redo log的打开、刷写模式,对于这个参数,文档上是这样描述的:有三个值:fdatasync(默认),O_DSYNC,O_DIRECT。

fdatasync模式:写数据时,write这一步并不需要真正写到磁盘才算完成(可能写入到操作系统buffer中就会返回完成),真正完成是flush操作,buffer交给操作系统去flush,并且文件的元数据信息也都需要更新到磁盘。

O_DSYNC模式:写日志操作是在write这步完成,而数据文件的写入是在flush这步通过fsync完成。

O_DIRECT模式:数据文件的写入操作是直接从mysql innodb buffer到磁盘的,并不用通过操作系统的缓冲,而真正的完成也是在flush这步,日志还是要经过OS缓冲。

未分类

注:在类unix操作系统中,文件的打开方式为O_DIRECT会最小化缓冲对io的影响,该文件的io是直接在用户空间的Buffer上操作的,并且io操作是同步的,因此不管是read()系统调用还是write()系统调用,数据都保证是从磁盘上读取的;O_SYNC方式表示以同步io的方式打开文件,任何写操作都将阻塞到数据写入物理磁盘后才返回。fsync(int filedes)函数只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fdatasync(int filedes)函数类似于fsync,但它只影响文件的数据部分。而除数据外,fsync还会同步更新文件的元信息到磁盘。

在Linux上使用本机AIO的noop或者deadline I/O调度

InnoDB使用Linux上的异步I/O子系统(本机AIO)对数据文件页执行预读和写请求。此行为由innodb_use_native_aio配置选项控制,配置选项默认启用。使用本机AIO,I/O调度器的类型对I/O性能有更大的影响。通常,建议使用noop或deadline I/O调度进行基准测试,以确定哪些I/O调度程序为你的工作负载和环境提供最佳结果。

考虑非旋转存储

非旋转存储通常为随机I/O操作提供更好的性能,以及用于顺序I/O操作的旋转存储。在旋转和非旋转存储设备上分发数据和日志文件时,请主要考虑对每个文件执行的I/O操作的类型。面向随机的I/O的文件通常包括每表文件和一般表空间数据文件,撤销表空间文件和临时表空间文件。面向顺序的I/O的文件包括InnoDB系统表空间文件(双重缓冲和插入缓冲)以及日志文件,如二进制日志文件和重做日志文件。

使用非旋转存储时,请查看以下配置选项的设置:

innodb_checksum_algorithm

该crc32选项使用更快的校验和算法,建议用于快速存储系统。

innodb_flush_neighbors

该选项可优化旋转存储设备的I/O,禁用它用于非旋转存储或旋转和非旋转存储的混合。

innodb_io_capacity

对于低端非旋转存储设备,默认设置200通常就足够了。对于高端,总线连接的设备,请考虑更高的设置,如1000。

innodb_io_capacity_max

默认值为2000,适用于使用非旋转存储的工作负载。对于高端,总线附加的非旋转存储设备,考虑更高的设置,如2500。

innodb_log_compressed_pages

如果重做日志在非旋转存储器上,请考虑禁用此选项以减少日志记录。请参阅禁用压缩页面的日志记录。

innodb_log_file_size

如果重做日志在非旋转存储上,请配置此选项以最大化缓存和写入组合。

innodb_page_size

考虑使用与磁盘的内部扇区大小相匹配的页面大小。早期的SSD设备通常具有4k扇区大小,一些较新的设备具有16k扇区大小。默认InnoDB页面大小为16k。保持页面大小接近存储设备块大小可将未更改的数据量重写到磁盘中。

binlog_row_image

如果二进制日志在非旋转存储上,并且所有表都具有主键,请考虑将此选项设置minimal为减少日志记录。确保为你的操作系统启用了TRIM支持。通常默认情况下启用。

增加I/O capacity以避免积压

如果吞吐量由于检查点操作而导致InnoDB周期性下降,请考虑增加innodb_io_capacity配置选项的值。当log cap(指未刷新到磁盘脏页的日志大小)大于整个日志空间的75%时,系统会异步的将log cap部分的日志涉及的脏页刷到磁盘上,但是此时事务提交不会终止,也就是说还允许有redo log的继续写入。但是如果log cap继续增加,当超过整个日志空间的90%时,MySQL会停止事务的更新,此时redo log也会停止写入,必须等到刷足够的脏页时,才能允许事务再次提交。本质上说,如果事务提交的速度大于脏页刷盘的速度,最终都会触发上述日志保护的功能,即最终系统停止事务的更新,来保证日志记录的脏页能够刷新到磁盘上。也就是说更高的值会导致更频繁的冲洗,避免积压的工作可能导致吞吐量下降。

在Fusion-io设备上存储系统表空间文件

通过在支持原子写入的Fusion-io设备上存储系统表空间文件(“ ibdata文件 ”),可以利用双写缓冲区相关的I/O优化。在这种情况下,双重缓冲(innodb_doublewrite)被自动禁用,并且Fusion-io原子写入用于所有数据文件。此功能仅在Fusion-io硬件上受支持,并且仅在Linux上启用Fusion-io NVMFS。要充分利用此功能,建议使用innodb_flush_method设置O_DIRECT。

注意:因为双写缓冲区设置是全局的,因此对非Fusion-io硬件上的数据文件也将禁用双缓冲。

禁止记录压缩页面

当使用InnoDB表压缩功能时,对压缩数据进行更改时,会将重新压缩页面的图像写入重做日志。此行为由innodb_log_compressed_pages控制,默认情况下启用此功能,以防止zlib在恢复期间使用不同版本的压缩算法时可能会发生损坏。如果你确定zlib版本不会更改,请禁用innodb_log_compressed_pages以减少修改压缩数据的重做日志生成的工作负载。

修改jenkins目录和端口及启动用户

当前系统Centos6.9,Jenkins版本2.60.2

由于jenkins默认目录安装在/var/lib/jenkins/下,更改目录,移到单独的一个磁盘上

jenkins默认配置文件在/etc/sysconfig/jenkins

目录端口及启动用户都是通过配置文件来更改的

1、修改jenkins目录

vi /etc/sysconfig/jenkins

将JENKINS_HOME=”/var/lib/jenkins”改成JENKINS_HOME=”/data/jenkins”

我这里修改jenkins目录到/data,创建对应的/data目录

mkdir /data

cp -a /var/lib/jenkins/ /data/

2、修改jenkins启动用户

vi /etc/sysconfig/jenkins

JENKINS_USER=”jenkins”改成JENKINS_USER=”root”

这样就是以root用户启动了

3、修改jenkins端口

vi /etc/sysconfig/jenkins

JENKINS_PORT=”8080″改成JENKINS_PORT=”8088″

最后保存,重启jenkins服务

service jenkins restart

这样jenkins端口就改成了8088

Linux下逐行比较两个文本文件

Linux很多情况下需要逐行比较两个文本文件,并显示出两个文件中所有不同的行。推荐使用diff和vimdiff(需要安装vim)

很多情况下需要逐行比较两个文本文件,并显示出两个文件中所有不同的行。推荐使用diff和vimdiff(需要安装vim)

vimdiff zhuzhan20170830 zhuzhan20170831
:qa! #退出

未分类

cat diffzhuzhan20170831
--- /root/checkdir/zhuzhan20170830  2017-08-30 07:05:03.250349499 +0800
+++ /root/checkdir/zhuzhan20170831  2017-08-31 07:05:02.523044481 +0800
@@ -93,10 +93,12 @@
367b63c971d06817126273caaf7520ff /baidu_push.php
47396f597f8b06e103c7d53014febc80 /vote/index.php
c509a365abe6ced5adb8d09ba59e993d /ViewClick/zt.php
+5d36bb18cf3d737672e856aa67281a2e /ViewClick/zhibojump.php
082a0ee07a3467779091d1d71980965f /ViewClick/ViewMore.php
-3dc18566881ef1420a6f8988cd1eefda /ViewClick/jump.php
-097555dcc2718ece2fac34070a14b1c2 /ViewClick/index.php
-ad168ec79acf8bf3861a099b08a7c0c8 /ViewClick/dell.php
+3dbe6a57c87f1d3d0fe6eef4bd1472bb /ViewClick/jump.php
+3d88ddf568f0279767cf671b10f63484 /ViewClick/index.php
+aa5ce6769a9a8e8e73a4c13aad24753f /ViewClick/dell.php
+40cd750bba9870f18aada2478b24840a /ViewClick/cacheZhiboJump.php
32c01ba72925465a3a437f41ffb9e4ee /uvonclick/index.php
477344028faf48e0cb70088a5cb4c266 /PPHD/index.php
f82b7e6311a0cff4af01092fd07a541f /onclick/index.php
--- /root/checkdir/zhuzhan20170830  2017-08-30 07:05:03.250349499 +0800
+++ /root/checkdir/zhuzhan20170831  2017-08-31 07:05:02.523044481 +0800
@@ -93,10 +93,12 @@

字母”a”、”d”、”c”分别表示添加、删除及修改操作

+ 比较的文件的后者比前着多一行
- 比较的文件的后者比前着少一行

显示结果分成三部分

第一部分是文件的基本信息,“—”表示变动前的文件,“+++”表示变动后的文件。

第二部分,变动的位置用两个@作为起首和结束。@@ -93,10 +93,12 @@,前面的“-93,10”分成三个部分:减号表示第一个文件(即f1),“93”表示第93行,“10”表示连续10行,
合在一起,就表示下面是第一个文件从第93行开始的连续10行。同样的,“+93,12”表示变动后,成为第二个文件从第93行开始的连续12行。

第三部分是变动的具体内容,它将两个文件的上下文,合并显示在一起,每一行最前面的标志位,空表示无变动,减号表示第一个文件删除的行,加号表示第二个文件新增的行。

diff的输出可以做为补丁

制作补丁:
  

diff -urN linux-3.4.2 linux-3.4.2-my > linux-3.4.2-my.patch

打补丁:
  

patch -p1 < ../linux-3.4.2-my.patch