Linux crontab设置定时重启Apache服务

通过 crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。

1.首先在 $HOME/.profile 中设置 crontab 使用的编辑器:

EDITOR=vi; export EDITOR

2.新建一个 crontab 文件, 比如 root 用户的话就叫做 rootcron, 写入以下内容 :
每天 4 点重启 httpd

00 4 * * * /usr/sbin/service httpd restart

这里遇到的问题就是, 在 centos 下直接用 serivce 无效, /usr/sbin/service 才生效.

3.直接 crontab 加上上面新建的文件 rootcron 即可提交 crontab 任务:

crontab rootcron

查看 crontab 任务:

crontab -l

zabbix监控k8s(kubernetes)pod到service的网络

最近k8s测试环境不时地出现无法访问一些service endpoint的问题。排查发现是某些节点的kube-proxy没有同步最新的service来配置iptables,至于为什么没有同步service,目前还不知道。不过现在先加对service的监控来及时发现问题,争取到更多时间排查吧。

部署sa-tool

sa-tool其实就是一个centos镜像容器,为了方便在上面执行一些检测网络的命令。我们这里使用daemonset来部署。

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: sa-tool
spec:
  template:
    metadata:
      labels:
        app: sa-tool
    spec:
      containers:
      - command:
        - /bin/sh
        - -c
        - while true; do sleep 3600; done
        name: sa-tool
        image: centos:7

      restartPolicy: Always
      nodeSelector:
        ibd/worktype: "true"

监控脚本

#!/bin/bash

cat /dev/null > /tmp/test-pod-network.tmp
for pod in `kubectl --request-timeout 5s get pod | awk 'NR>1{print $1}'`;do
    for addr in `kubectl --request-timeout 5s get service  --all-namespaces -o=custom-columns=NAME:metadata.name,NAMESPACE:metadata.namespace,PORT:spec.ports[0].port | awk 'NR>1{print ""$1"."$2".svc.ibd.lan/"$3}'`;do
        if ! kubectl --request-timeout 5s exec -i $pod -- timeout 3 bash -c 'exec 3<> /dev/tcp/'$addr 2>/dev/null;then
            echo "$pod $addr" >> /tmp/test-pod-network.tmp
        fi    
    done
done
cp /tmp/test-pod-network.tmp /tmp/test-pod-network.log

配置key

打开agent配置文件,加入如下key

UserParameter=pod.network, cat /tmp/test-pod-network.log

配置监控项

登录zabbix后台,添加如下监控项:

pod.network

k8s与flannel网络原理

我们这里假设flannel使用VXLAN协议。每台主机都安装有flannel。k8s定义的flannel网络为10.0.0.0/16,各主机的flannel从这个网络申请一个子网。pod1所在的主机的flannel子网为10.0.14.1/24,pod2所在主机的flannel子网为10.0.5.1/24。每台主机有cni0和flannel.1虚拟网卡。cni0为在同一主机pod共用的网桥,当kubelet创建容器时,将为此容器创建虚拟网卡vethxxx,并桥接到cni0网桥。flannel.1是一个tun虚拟网卡,接收不在同一主机的POD的数据,然后将收到的数据转发给flanneld进程。原理图:
系统管理

pod1到pod2的网络

pod1路由表:

default via 10.0.14.1 dev eth0 
10.0.0.0/16 via 10.0.14.1 dev eth0 
10.0.14.0/24 dev eth0  proto kernel  scope link  src 10.0.14.15 

host1路由表:

default via 192.168.93.254 dev eno16777984  proto static  metric 100 
10.0.0.0/16 dev flannel.1 
10.0.14.0/24 dev cni0  proto kernel  scope link  src 10.0.14.1 
172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1 
192.168.93.0/24 dev eno16777984  proto kernel  scope link  src 192.168.93.212  metric 100 

pod1 IP地址:10.0.14.15
pod2 IP地址:10.0.5.150

pod1与pod2不在同一台主机

下面是从pod1 ping pod2的数据包流向
1. pod1(10.0.14.15)向pod2(10.0.5.150)发送ping,查找pod1路由表,把数据包发送到cni0(10.0.14.1)
2. cni0查找host1路由,把数据包转发到flannel.1
3. flannel.1虚拟网卡再把数据包转发到它的驱动程序flannel
4. flannel程序使用VXLAN协议封装这个数据包,向api-server查询目的IP所在的主机IP,称为host2(不清楚什么时候查询)
5. flannel向查找到的host2 IP的UDP端口8472传输数据包
6. host2的flannel收到数据包后,解包,然后转发给flannel.1虚拟网卡
7. flannel.1虚拟网卡查找host2路由表,把数据包转发给cni0网桥,cni0网桥再把数据包转发给pod2
8. pod2响应给pod1的数据包与1-7步类似
下面是这次ping数据包的wireshark解析出的协议数据:
pod1 ping请求:
系统管理
pod2响应:
系统管理

pod1与pod2在同一台主机

pod1和pod2在同一台主机的话,由cni0网桥直接转发请求到pod2,不需要经过flannel。

pod到service的网络

创建一个service时,相应会创建一个指向这个service的域名,域名规则为{服务名}.{namespace}.svc.{集群名称}。之前service ip的转发由iptables和kube-proxy负责,目前基于性能考虑,全部为iptables维护和转发。iptables则由kubelet维护。service仅支持udp和tcp协议,所以像ping的icmp协议是用不了的,所以无法ping通service ip。
现在我们尝试看看在pod1向kube-dns的service ip 10.16.0.10:53发送udp请求是如何转发的。
我们先找出与此IP相关的iptables规则:

【PREROUTING链】
-m comment --comment "kubernetes service portals" -j KUBE-SERVICES

【KUBE-SERVICES链】
-d 10.16.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU

【KUBE-SVC-TCOU7JCQXEZGVUNU链】
-m comment --comment "kube-system/kube-dns:dns" -j KUBE-SEP-L5MHPWJPDKD7XIFG

【KUBE-SEP-L5MHPWJPDKD7XIFG链】
-p udp -m comment --comment "kube-system/kube-dns:dns" -m udp -j DNAT --to-destination 10.0.0.46:53
  1. pod1向service ip 10.16.0.10:53发送udp请求,查找路由表,把数据包转发给网桥cni0(10.0.14.1)
  2. 在数据包进入cnio网桥时,数据包经过PREROUTING链,然后跳至KUBE-SERVICES链
  3. KUBE-SERVICES链中一条匹配此数据包的规则,跳至KUBE-SVC-TCOU7JCQXEZGVUNU链
  4. KUBE-SVC-TCOU7JCQXEZGVUNU不做任何操作,跳至KUBE-SEP-L5MHPWJPDKD7XIFG链
  5. KUBE-SEP-L5MHPWJPDKD7XIFG里对此数据包作了DNAT到10.0.0.46:53,其中10.0.0.46即为kube-dns的pod ip
  6. 查找与10.0.0.46匹配的路由,转发数据包到flannel.1
  7. 之后的数据包流向就与上面的pod1到pod2的网络一样了

pod到外网

  1. pod向qq.com发送请求
  2. 查找路由表,转发数据包到宿主的网卡
  3. 宿主网卡完成qq.com路由选择后,iptables执行MASQUERADE,把源IP更改为宿主网卡的IP
  4. 向qq.com服务器发送请求

CDNFly安装

支持的系统

  • centos-7
  • centos-6 (不推荐使用)
  • ubuntu 16.04
  • ubuntu 14.04

环境要求

需要干净的系统,可能会覆盖原有的mysql

主控端安装

curl -k "http://devops.webres.wang/httpguard/master.sh?$(date +%s)" -o master.sh
chmod +x master.sh
./master.sh

被控节点安装

curl -k "http://devops.webres.wang/httpguard/agent.sh?$(date +%s)" -o agent.sh
chmod +x agent.sh
./agent.sh 主控端IP

请把上面命令的主控端IP替换为真实的IP,如127.0.0.1

主控端访问

地址:http://ip:88/
用户和密码:admin guard

使用zabbix监控K8s(kubernetes)异常POD

通过监控异常的pod,及时发现k8s存在的问题。原理是使用kubectl get pod –all-namespaces,找到ready列,如果ready数量与desire数量不一致的,或者非RUNNING的状态POD,则认为这个pod异常,,进而告警。

配置agent监控项

/etc/zabbix/zabbix_agentd.d/k8s.conf

UserParameter=abnormal.pod, kubectl get pod --all-namespaces -o wide | awk 'NR>1{cmd="echo "$3" | bc";cmd|getline ret;close(cmd);if (ret != 1 || $4 != "Running"){print}}'

导入模板

<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
    <version>3.2</version>
    <date>2017-06-16T03:51:42Z</date>
    <groups>
        <group>
            <name>Templates</name>
        </group>
    </groups>
    <templates>
        <template>
            <template>Template App K8s</template>
            <name>Template App K8s</name>
            <description/>
            <groups>
                <group>
                    <name>Templates</name>
                </group>
            </groups>
            <applications>
                <application>
                    <name>k8s</name>
                </application>
            </applications>
            <items>
                <item>
                    <name>abnormal pod</name>
                    <type>0</type>
                    <snmp_community/>
                    <multiplier>0</multiplier>
                    <snmp_oid/>
                    <key>abnormal.pod</key>
                    <delay>60</delay>
                    <history>90</history>
                    <trends>0</trends>
                    <status>0</status>
                    <value_type>4</value_type>
                    <allowed_hosts/>
                    <units/>
                    <delta>0</delta>
                    <snmpv3_contextname/>
                    <snmpv3_securityname/>
                    <snmpv3_securitylevel>0</snmpv3_securitylevel>
                    <snmpv3_authprotocol>0</snmpv3_authprotocol>
                    <snmpv3_authpassphrase/>
                    <snmpv3_privprotocol>0</snmpv3_privprotocol>
                    <snmpv3_privpassphrase/>
                    <formula>1</formula>
                    <delay_flex/>
                    <params/>
                    <ipmi_sensor/>
                    <data_type>0</data_type>
                    <authtype>0</authtype>
                    <username/>
                    <password/>
                    <publickey/>
                    <privatekey/>
                    <port/>
                    <description/>
                    <inventory_link>0</inventory_link>
                    <applications>
                        <application>
                            <name>k8s</name>
                        </application>
                    </applications>
                    <valuemap/>
                    <logtimefmt/>
                </item>
            </items>
            <discovery_rules/>
            <httptests/>
            <macros/>
            <templates/>
            <screens/>
        </template>
    </templates>
    <triggers>
        <trigger>
            <expression>{Template App K8s:abnormal.pod.strlen()}<>0</expression>
            <recovery_mode>0</recovery_mode>
            <recovery_expression/>
            <name>abnormal pod</name>
            <correlation_mode>0</correlation_mode>
            <correlation_tag/>
            <url/>
            <status>0</status>
            <priority>2</priority>
            <description/>
            <type>0</type>
            <manual_close>0</manual_close>
            <dependencies/>
            <tags/>
        </trigger>
    </triggers>
</zabbix_export>

应用模板

找到能执行kubectl的节点,添加k8s模板。

使用fluentd实现实时收集日志文件

目前线上服务使用了k8s进行部署,一个服务配置了多个副本,然后日志是挂载到宿主机器的目录的,所以当服务部署到三台机器时,这时要查看业务日志,就必须依次登录三台服务器来看日志。显然,这非常地不方便。团队想把日志收集到一个地方统一查看。于是开始尝试各种方案。

尝试

1. elasticsearch + fluentd + in_tail(input) + fluent-plugin-elasticsearch(output) + kibana

刚开始就测试使用网络上推荐的日志收集方案,elasticsearch + fluentd + kibana,部署完成后,经过使用,并不能很方便地对日志进行检索,因为日志格式非常多,不方便对日志进行格式化,所以收集过来的日志并不是结构化的。另一个原因是elasticsearch占用的CPU很高,这个不知道什么原因,可能给的资源不够或配置不当。不过更主要是团队成员希望最好是直接把日志收集到一台服务器,然后能够使用linux的工具,如grep,awk,less来查询日志,所以最终放弃此方案。

2. rsyslog + fluentd + in_tail(input) + fluent-plugin-remote_syslog(output)
然后开始尝试使用fluentd来收集日志并发送到rsyslog, rsyslog使用fluentd发送过来的tag来命令文件名,但由于syslog协议的限制,tag最大为32个字符,最终无奈放弃此方案。

3. fluentd-agent(input: in_tail, output: forward) fluentd-server(input: forward, ouput: fluent-plugin-forest)
最后采用agent和server端都使用fluentd,agent端的input使用in_tail,ouput使用forward,server端的input使用forward,ouput使用fluent-plugin-forest,找到fluent-plugin-forest这个插件不容易,因为它支持以tag命名文件名,并非常稳定,其它的插件由于不怎么更新了,bug挺多无法使用。

部署

server端

docker run -d  -p 24224:24224 -p 24224:24224/udp -v /var/log/worker:/var/log/worker -v /etc/localtime:/etc/localtime --name fluent-server registry.cn-hangzhou.aliyuncs.com/shengjing/fluent-server

agent端

在每个agent新建一个/home/fluent目录,并设置权限为777

mkdir -p /home/fluent
chmod 777 /home/fluent

这里我们使用k8s的damonset来部署

kubectl create -f fluentd-daemonset.yaml

fluentd-daemonset.yaml:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  template:
    metadata:
      labels:
        k8s-app: fluentd-logging
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: fluentd
        image: registry.cn-hangzhou.aliyuncs.com/shengjing/fluent-client
        imagePullPolicy: Always
        env:
          - name:  RSYSLOG_HOST
            value: "10.29.112.24"
        resources:
          limits:
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 500Mi
        volumeMounts:
        - name: datlog
          mountPath: /dat/log
          readOnly: true
        - name: fluent
          mountPath: /home/fluent
        - name: localtime
          mountPath: /etc/localtime
          readOnly: true

      terminationGracePeriodSeconds: 30
      volumes:
      - name: datlog
        hostPath:
          path: /dat/log
      - name: fluent
        hostPath:
          path: /home/fluent

      - name: localtime
        hostPath:
          path: /etc/localtime
  • 其中192.168.93.201为fluentd server的ip

Dockerfile

server端

Dockerfile:

FROM fluent/fluentd:v0.12-debian
COPY entrypoint.sh /bin/entrypoint.sh
RUN  fluent-gem install  fluent-plugin-forest 
     && chmod +x /bin/entrypoint.sh
COPY fluent.conf /fluentd/etc/

entrypoint.sh:

#!/usr/bin/dumb-init /bin/sh

uid=${FLUENT_UID:-1000}

# check if a old fluent user exists and delete it
cat /etc/passwd | grep fluent
if [ $? -eq 0 ]; then
    deluser fluent
fi

# (re)add the fluent user with $FLUENT_UID
useradd -u ${uid} -o -c "" -m fluent
export HOME=/home/fluent

# chown home and data folder
chown -R fluent /home/fluent
chown -R fluent /fluentd

gosu fluent "$@"

fluent.conf:

<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match log.**>
  @type forest
  subtype file
  <template>
    time_slice_format %Y%m%d
    path /var/log/worker/${tag_parts[3..-2]}
    format single_value
    flush_interval 2s
    buffer_path /tmp/buffer/${tag_parts[3..-2]}
    append true
    num_threads 1
  </template>
</match>

agent端

Dockerfile:

FROM fluent/fluentd:v0.12-debian
COPY entrypoint.sh /bin/entrypoint.sh
RUN  chmod +x /bin/entrypoint.sh
COPY fluent.conf /fluentd/etc/

entrypoint.sh:

#!/usr/bin/dumb-init /bin/sh

uid=${FLUENT_UID:-1000}

# check if a old fluent user exists and delete it
cat /etc/passwd | grep fluent
if [ $? -eq 0 ]; then
    deluser fluent
fi

# (re)add the fluent user with $FLUENT_UID
useradd -u ${uid} -o -c "" -m fluent
export HOME=/home/fluent

# chown home and data folder
chown -R fluent /home/fluent
chown -R fluent /fluentd

# replace FLUENTD_SERVER_HOST
sed -i "s/FLUENTD_SERVER_HOST/$FLUENTD_SERVER_HOST/" /fluentd/etc/fluent.conf

gosu fluent "$@"

fluent.conf:

<source>
  @type tail
  path /dat/log/**/*.log
  tag log.*
  format none
  refresh_interval 5
  read_from_head true
  limit_recently_modified 86400
  pos_file /home/fluent/dat-log.pos
</source>
<match log.**>
  @type forward
  <server>
    name myserver1
    host FLUENTD_SERVER_HOST
    port 24224
    weight 60
  </server>
  buffer_type file
  buffer_path /tmp/buffer_file
  flush_interval 2s
  buffer_chunk_limit 8m
  buffer_queue_limit 1000
  num_threads 4
</match>

参考

https://github.com/tagomoris/fluent-plugin-forest
http://docs.fluentd.org/v0.12/articles/in_tail
http://docs.fluentd.org/v0.12/articles/out_forward
http://docs.fluentd.org/v0.12/articles/in_forward
http://docs.fluentd.org/v0.12/articles/out_file

CentOS-7使用kubeadm安装配置k8s(kubernetes)

kubeadm配置k8s

时间有限,只是列出了安装k8s的配置步骤,没有写明为什么这样做,以后有空再补上。如果有什么不明白的,欢迎在评论下留下你的问题,有空会回复。

服务器规划

服务器名称 角色 IP地址 系统
master master 192.168.83.133 CentOS 7.3
kworker1 worker 192.168.83.134 CentOS 7.3
kworker2 worker 192.168.83.135 CentOS 7.3

配置hostname

如配置master的hostname
1. 在/etc/hosts添加127.0.0.1 master记录
2. echo “master” > /etc/hostname
3. hostname master

配置iptables

所有机器执行

chmod +x /etc/rc.d/rc.local
iptables -I INPUT -s 192.168.83.0/24 -j ACCEPT
iptables -I FORWARD -j ACCEPT
echo 'iptables -I INPUT -s 192.168.83.0/24 -j ACCEPT' >> /etc/rc.d/rc.local
echo 'iptables -I FORWARD -j ACCEPT' >> /etc/rc.d/rc.local

其中192.168.83.0/24为k8s节点的所在IP段。

安装docker,kubelet等

所有机器运行。
更新系统

yum -y update

设置aliyun的源

cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF

关闭selinux

sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0

安装kubelet和docker

yum install -y docker kubelet kubeadm kubectl kubernetes-cni
systemctl enable docker
systemctl start docker
systemctl enable kubelet

配置kubelet的源为aliyun(此aliyun源为本人从google的源搬过来的)

cat > /etc/systemd/system/kubelet.service.d/20-pod-infra-image.conf <<EOF
[Service]
Environment="KUBELET_EXTRA_ARGS=--pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/centos-bz/pause-amd64:3.0"
EOF
systemctl daemon-reload
systemctl restart kubelet
reboot

配置内核

所有机器运行。

cat <<EOF > /etc/sysctl.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl -p

镜像版本

由于国内无法访问google源,所以本人把主要的k8s镜像搬到了aliyun。而etcd和dns,pause的版本可以在源码的以下路径找到。可以根据相应的版本创建源。
– etcd版本:cmd/kubeadm/app/images/images.go
– dns版本:cmd/kubeadm/app/phases/addons/manifests.go
– pause-amd64: cmd/kubeadm/app/master/templates.go:

初始化master

在master运行

初始化

初始化k8s并锁定v1.6.2版本。

export KUBE_REPO_PREFIX=registry.cn-hangzhou.aliyuncs.com/centos-bz
kubeadm init --pod-network-cidr 10.244.0.0/16 --kubernetes-version=v1.6.2

拉取镜像需要一定的时间,可以在master执行docker images查看已下载的镜像。

配置KUBECONFIG环境变量

cp -f /etc/kubernetes/admin.conf $HOME/
chown $(id -u):$(id -g) $HOME/admin.conf
export KUBECONFIG=$HOME/admin.conf
echo "export KUBECONFIG=$HOME/admin.conf" >>  ~/.bash_profile

允许master运行pod

如果需要master作为worker运行pod,执行

kubectl taint nodes --all node-role.kubernetes.io/master-

添加node

在worker1和worker2执行。

kubeadm join --token a30d18.1600388a52b3b472 192.168.83.133:6443

以上命令为在master初始化成功后在控制台输出的命令。
查看pod运行情况:

kubectl get pods -n kube-system

安装flannel

kubectl create -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel-rbac.yml
curl -sSL "https://github.com/coreos/flannel/blob/master/Documentation/kube-flannel.yml?raw=true" | kubectl create -f -

安装dashboard

curl -sSL https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/kubernetes-dashboard.yaml | sed 's#gcr.io/google_containers#registry.cn-hangzhou.aliyuncs.com/centos-bz#' | kubectl create -f -

执行如下命令来找到dashboard的端口:

[root@master ~]# kubectl get service  -n kube-system | grep kubernetes-dashboard
kubernetes-dashboard   10.100.79.47   <nodes>       80:32574/TCP    4h

如上端口为32574,可以在浏览器打开http://worker1:32574访问控制面板。

安装nginx ingress controller

安装default backend

curl -sSL https://raw.githubusercontent.com/kubernetes/ingress/nginx-0.9.0-beta.5/examples/deployment/nginx/default-backend.yaml | sed 's#gcr.io/google_containers#registry.cn-hangzhou.aliyuncs.com/centos-bz#' |  kubectl apply -f -

安装nginx ingress controller

新建ingress-rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ingress
  namespace: kube-system

---

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: ingress
subjects:
  - kind: ServiceAccount
    name: ingress
    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

新建nginx-ingress.yml:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: nginx-ingress-lb
  labels:
    name: nginx-ingress-lb
  namespace: kube-system
spec:
  template:
    metadata:
      labels:
        name: nginx-ingress-lb
      annotations:
        prometheus.io/port: '10254'
        prometheus.io/scrape: 'true'
    spec:
      terminationGracePeriodSeconds: 60
      serviceAccountName: ingress
      hostNetwork: true
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/centos-bz/nginx-ingress-controller:0.9.0-beta.5
        name: nginx-ingress-lb
        readinessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
        livenessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          timeoutSeconds: 1
        ports:
        - containerPort: 80
          hostPort: 80
        - containerPort: 443
          hostPort: 443
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        args:
        - /nginx-ingress-controller
        - --default-backend-service=$(POD_NAMESPACE)/default-http-backend

创建nginx ingress controller

kubectl create -f ingress-rbac.yaml
kubectl apply -f nginx-ingress.yml

在worker1和worker2使用ss -nlpt命令查看80和443端口是否已经监听。

测试

测试ingress

部署echo server

kubectl run echoheaders --image=registry.cn-hangzhou.aliyuncs.com/centos-bz/echoserver:1.4 --replicas=1 --port=8080

新建service

kubectl expose deployment echoheaders --port=80 --target-port=8080 --name=echoheaders-x
kubectl expose deployment echoheaders --port=80 --target-port=8080 --name=echoheaders-y

新建ingress.yaml:

# An Ingress with 2 hosts and 3 endpoints
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: echomap
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: echoheaders-x
          servicePort: 80
  - host: bar.baz.com
    http:
      paths:
      - path: /bar
        backend:
          serviceName: echoheaders-y
          servicePort: 80
      - path: /foo
        backend:
          serviceName: echoheaders-x
          servicePort: 80

新建规则

kubectl create -f ingress.yaml

使用curl测试:

curl 192.168.83.135/foo -H 'Host: foo.bar.com'
curl 192.168.83.135/other -H 'Host: foo.bar.com'

pod-service

todo

external-to-service

todo

pod-to-pod

todo

dns测试

todo

编译支持mysql-5.1.73版本的xtrabackup

一、基础介绍

mysql5.1在源码中配备了两个版本的innodb存储引擎源码:innobase和innodb_plugin,编译安装的时候可以通过参数–with-plugins=innobase,innodb_plugin来指定是否将innodb存储引擎引入,具体这两个参数引入对编译后的mysql产生怎样的差异,后面再做解析。然而对于PerconaXtraBackup,在release版本中有2.0,2.1,2.2三个大版本,然后每个版本都是与mysql发布的innodb存储引擎版本对应,举例来说:

percona-xtrabackup-2.0.8在BUILD.txt中指定了utils/build.sh参数如下:

The script needs the codebase for which the building is targeted, you must
provide it with one of the following values or aliases:

  ================== =========  =============================================
  Value              Alias      Server
  ================== =========  =============================================
  innodb51_builtin   5.1    build against built-in InnoDB in MySQL 5.1
  innodb51           plugin build against InnoDB plugin in MySQL 5.1
  innodb55           5.5    build against InnoDB in MySQL 5.5
  xtradb51           xtradb     build against Percona Server with XtraDB 5.1
  xtradb55           xtradb55   build against Percona Server with XtraDB 5.5
  innodb56           5.6        build against InnoDB in MySQL 5.6
  ================== =========  =============================================

而在percona-xtrabackup-2.1.9在BUILD.txt中指定了utils/build.sh参数如下:

The script needs the codebase for which the building is targeted, you must
provide it with one of the following values or aliases:

  ================== =========  =============================================
  Value              Alias      Server
  ================== =========  =============================================
  innodb51           plugin build against InnoDB plugin in MySQL 5.1
  innodb55           5.5    build against InnoDB in MySQL 5.5
  xtradb51           xtradb     build against Percona Server with XtraDB 5.1
  xtradb55           xtradb55   build against Percona Server with XtraDB 5.5
  innodb56           5.6        build against InnoDB in MySQL 5.6
  ================== =========  =============================================

解释一下:从percona-xtrabackup2.1开始取消对innodb built-in版本支持,即:用Percona-Xtrabackup2.1备份5.1built-in版本将会出现如下报错:

innobackupex: Error: Support for MySQL 5.1 with builtin InnoDB (not the plugin) was removed in Percona XtraBackup 2.1. The last version to support MySQL 5.1 with builtin InnoDB was Percona XtraBackup 2.0.

二、Percona-Xtrabackup2.0.8编译安装

cd /tmp
wget https://github.com/percona/percona-xtrabackup/archive/percona-xtrabackup-2.0.8.tar.gz
tar xf percona-xtrabackup-2.0.8.tar.gz
cd percona-xtrabackup-percona-xtrabackup-2.0.8
wget http://downloads.mysql.com/archives/mysql-5.1/mysql-5.1.59.tar.gz 
./utils/build.sh 5.1

编译安装完,备份工具保存在 src/xtrabackup_51,打包xtrabackup_51和innobackupex 到目的服务器的/usr/local/bin目录下,即可执行备份

使用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")
    }
}