环境

CentOS Linux release 7.5.1804

安装docker-machine

base=https://github.com/docker/machine/releases/download/v0.14.0 &&
  curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine &&
  sudo install /tmp/docker-machine /usr/local/bin/docker-machine

docker-machine -v

安装 virtualbox

将ol7_addons(Oracle Linux的源)添加到yum源

cat << EOF | sudo tee /etc/yum.repos.d/ol7_addons.repo
[ol7_addons]
name=Oracle Linux $releasever Add ons (\$basearch)
baseurl=http://public-yum.oracle.com/repo/OracleLinux/OL7/addons/\$basearch/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-oracle
gpgcheck=1
enabled=1
EOF
# Verify this manually if you're paranoid like me
sudo rpm --import http://public-yum.oracle.com/RPM-GPG-KEY-oracle-ol7
sudo yum makecache

安装VirtualBox

yum -y install VirtualBox-5.2
yum -y install kernel-devel kernel-devel-3.10.0-693.el7.x86_64

查看系统内核版本

➜  ~ uname -sr
Linux 3.10.0-862.el7.x86_64

ftp://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/repoview/kernel-devel.html
下载并安装指定的版本

wget ftp://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/Packages/kernel-devel-3.10.0-862.el7.x86_64.rpm
yum -y install kernel-devel-3.10.0-862.el7.x86_64.rpm

更新

sudo /sbin/vboxconfig

创建 default machine

创建名为default的machine

docker-machine create --driver virtualbox default

如果提示

Error with pre-create check: "This computer doesn't have VT-X/AMD-v enabled. Enabling it in the BIOS is mandatory"

则需要关闭虚拟机,在VMware设置中开启虚拟化
VMware 配置

博客更新地址

环境

CentOS Linux release 7.5.1804

Python 3.6.4/2.7.14

简介

Airflow 是 Airbnb 开源的一个用 Python 编写的工作流管理平台,自带 web UI 和调度,目前在Apache下做孵化。

Airflow 管理页面

Airflow 中有两个基本概念,DAG和task。
DAG是多个task的集合,定义在一个Python文件中,包含了task之间的依赖关系,如task A在task B之后执行,task C可以单独执行等等。

安装并运行

# 默认目录在~/airflow,也可以使用以下命令来指定目录
export AIRFLOW_HOME=~/airflow

pip install apache-airflow

# 初始化数据库
airflow initdb

# 启动web服务,默认端口为8080,也可以通过`-p`来指定
airflow webserver -p 8080

# 启动 scheduler
airflow scheduler

定义第一个DAG

$AIRFLOW_HOME目录下新建dags文件夹,后面的所有dag文件都要存储在这个目录。

新建dag文件hello_world.py,语句含义见注释

# coding: utf-8

from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from datetime import datetime, timedelta


# 定义默认参数
default_args = {
    'owner': 'airflow',  # 拥有者名称
    'start_date': datetime(2018, 6, 6, 20, 00),  # 第一次开始执行的时间,为格林威治时间,为了方便测试,一般设置为当前时间减去执行周期
    'email': ['shelmingsong@gmail.com'],  # 接收通知的email列表
    'email_on_failure': True,  # 是否在任务执行失败时接收邮件
    'email_on_retry': True,  # 是否在任务重试时接收邮件
    'retries': 3,  # 失败重试次数
    'retry_delay': timedelta(seconds=5)  # 失败重试间隔
}

# 定义DAG
dag = DAG(
    dag_id='hello_world',  # dag_id
    default_args=default_args,  # 指定默认参数
    # schedule_interval="00, *, *, *, *"  # 执行周期,依次是分,时,天,月,年,此处表示每个整点执行
    schedule_interval=timedelta(minutes=1)  # 执行周期,表示每分钟执行一次
)


# 定义要执行的Python函数1
def hello_world_1():
    current_time = str(datetime.today())
    with open('/root/tmp/hello_world_1.txt', 'a') as f:
        f.write('%s\n' % current_time)
    assert 1 == 1  # 可以在函数中使用assert断言来判断执行是否正常,也可以直接抛出异常


# 定义要执行的Python函数2
def hello_world_2():
    current_time = str(datetime.today())
    with open('/root/tmp/hello_world_2.txt', 'a') as f:
        f.write('%s\n' % current_time)


# 定义要执行的Python函数3
def hello_world_3():
    current_time = str(datetime.today())
    with open('/root/tmp/hello_world_3.txt', 'a') as f:
        f.write('%s\n' % current_time)

# 定义要执行的task 1
t1 = PythonOperator(
    task_id='hello_world_1',  # task_id
    python_callable=hello_world_1,  # 指定要执行的函数
    dag=dag,  # 指定归属的dag
    retries=2,  # 重写失败重试次数,如果不写,则默认使用dag类中指定的default_args中的设置
)

# 定义要执行的task 2
t2 = PythonOperator(
    task_id='hello_world_2',  # task_id
    python_callable=hello_world_2,  # 指定要执行的函数
    dag=dag,  # 指定归属的dag
)

# 定义要执行的task 3
t3 = PythonOperator(
    task_id='hello_world_3',  # task_id
    python_callable=hello_world_3,  # 指定要执行的函数
    dag=dag,  # 指定归属的dag
)

t2.set_upstream(t1)
# 表示t2这个任务只有在t1这个任务执行成功时才执行,
# 等价于 t1.set_downstream(t2)
# 同时等价于 dag.set_dependency('hello_world_1', 'hello_world_2')

t3.set_upstream(t1)  # 同理

写完后执行它检查是否有错误,如果命令行没有报错,就表示没问题。

python $AIRFLOW_HOME/dags/hello_world.py

通过以下命令查看生效的dags

[root@localhost dags]# airflow list_dags
[2018-06-06 21:03:25,808] {__init__.py:45} INFO - Using executor SequentialExecutor
[2018-06-06 21:03:25,877] {models.py:189} INFO - Filling up the DagBag from /root/airflow/dags


-------------------------------------------------------------------
DAGS
-------------------------------------------------------------------
hello_world

查看hello_world这个dag下面的tasks

[root@localhost dags]# airflow list_tasks hello_world
[2018-06-06 21:04:45,736] {__init__.py:45} INFO - Using executor SequentialExecutor
[2018-06-06 21:04:45,805] {models.py:189} INFO - Filling up the DagBag from /root/airflow/dags
hello_world_1
hello_world_2
hello_world_3

查看hello_world这个dag下面tasks的层级关系

[root@localhost dags]# airflow list_tasks hello_world --tree
[2018-06-06 21:05:42,956] {__init__.py:45} INFO - Using executor SequentialExecutor
[2018-06-06 21:05:43,020] {models.py:189} INFO - Filling up the DagBag from /root/airflow/dags
<Task(PythonOperator): hello_world_2>
    <Task(PythonOperator): hello_world_1>
<Task(PythonOperator): hello_world_3>
    <Task(PythonOperator): hello_world_1>

如果按照以上步骤启动了schedule,则DAG已经开始定时执行了,我们设置了每分钟执行一次,可以访问your_domain:8080来查看任务的执行情况。
也可以查看/root/tmp/hello_world_1.txt/root/tmp/hello_world_2.txt/root/tmp/hello_world_3.txt文件中的内容来检查任务是否执行成功。

执行失败时email通知

如果需要在任务执行失败(执行过程中有异常抛出)的时候邮件通知,除了在DAG文件中指定接收email列表外,还需要在配置文件中指定发送邮箱的信息,打开配置文件$AIRFLOW_HOME/airflow.cfg,修改以下配置项,修改完需要重启webserver和schedule

smtp_host = smtp.163.com  # smtp邮箱地址
smtp_starttls = True  # 是否tls加密
smtp_mail_from = demo@163.com  # 发件人邮箱地址,需开通smtp服务
smtp_ssl = False  # 是否ssl加密
smtp_port = 25  # smtp端口号

使用位位移来指定执行顺序

以下四行的作用是相同的

op1 >> op2
op1.set_downstream(op2)

op2 << op1
op2.set_upstream(op1)

也可以连续使用位位移

op1 >> op2 >> op3 << op4

以上等价于

op1.set_downstream(op2)
op2.set_downstream(op3)
op3.set_upstream(op4)

使用变量(Variables)

变量的value可以在UI界面的Admin > Variables里面进行增删改查

可以在代码中这样使用变量

from airflow.models import Variable
foo = Variable.get("foo", default_var='a')  # 设置当获取不到时使用的默认值
bar = Variable.get("bar", deserialize_json=True)  # 对json数据进行反序列化

更多

Airflow doc

博客更新地址

环境

CentOS Linux release 7.5.1804

Q

新安装CentOS后,为了方便远程ssh,想设置静态IP,但是在/etc/sysconfig/network-scripts/下面只有ifcfg-lo这张Local Loopback网卡的配置文件,并没有我们想要的enp0s3(不同机器的网卡名不一样,也可能是ens33或其它名字)网卡配置文件,想要配置静态IP根本无从下手。

[root@localhost ~]# cd /etc/sysconfig/network-scripts/
[root@localhost network-scripts]# ls
ifcfg-lo     ifdown-post      ifup          ifup-plip    ifup-TeamPort
ifdown       ifdown-ppp       ifup-aliases  ifup-plusb   ifup-tunnel
ifdown-bnep  ifdown-routes    ifup-bnep     ifup-post    ifup-wireless
ifdown-eth   ifdown-sit       ifup-eth      ifup-ppp     init.ipv6-global
ifdown-ippp  ifdown-Team      ifup-ippp     ifup-routes  network-functions
ifdown-ipv6  ifdown-TeamPort  ifup-ipv6     ifup-sit     network-functions-ipv6
ifdown-isdn  ifdown-tunnel    ifup-isdn     ifup-Team

A

  • 使用nmcli con show查看网卡的UUID
[root@localhost network-scripts]# nmcli con show
NAME        UUID                                  TYPE      DEVICE  
enp0s3  ad633ea8-e358-255e-a167-6c14742b347d  ethernet  enp0s3 
  • /etc/sysconfig/network-scripts/目录下新建ifcfg-enp0s3(网卡名改成自己机器上的网卡名)
vim ifcfg-enp0s3  # 文件名改为自己的网卡名
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=enp0s3  # 改为自己的网卡名
UUID=ad633ea8-e358-255e-a167-6c14742b347d  # 改为自己的UUID
DEVICE=enp0s3  # 改为自己的网卡名
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.0.222  # 改为自己想要的IP,前三段保持和原来的IP一致
NETMASK=255.255.255.0
GATEWAY=192.168.0.1  # 一般为x.x.x.1,具体可在物理机上使用ipconfig命令查看
DNS1=114.114.114.114
DNS2=114.114.115.115
  • 配置文件保存后重启网络服务
systemctl restart network
  • 查看网络信息
[root@localhost network-scripts]# ifconfig
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.222  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 de12::f1a4:3694:6189:d250 prefixlen 64  scopeid 0x20<link>
        ether 05:01:24:0a:54:15  txqueuelen 1000  (Ethernet)
        RX packets 221235  bytes 325157863 (310.0 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 129166  bytes 10537641 (10.0 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 40  bytes 3528 (3.4 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 40  bytes 3528 (3.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
  • 惯例,调戏下百度
[root@localhost network-scripts]# ping www.baidu.com
PING www.a.shifen.com (115.239.211.112) 56(84) bytes of data.
64 bytes from 115.239.211.112 (115.239.211.112): icmp_seq=1 ttl=54 time=12.0 ms
64 bytes from 115.239.211.112 (115.239.211.112): icmp_seq=2 ttl=54 time=11.9 ms
  • 搞定

博客更新地址

环境

CentOS Linux release 7.5.1804

脚本完成操作

  • 切换CentOS软件镜像源为中科大软件源
  • 设置防火墙,允许全部端口通过
  • 安装git
  • 安装Pyenv包管理工具,Pyenv使用详见:Python版本管理工具 Pyenv的安装与使用
  • 安装Python 3.6.4
  • 切换pip源为豆瓣
  • 安装Docker
  • 安装MySQL
  • 初始化MySQL(root@localhost的密码为123456,song@%的密码为123456)
  • 设置sshd开机启动
  • 安装ntp时间自动更新工具

使用

  • Github下载部署脚本代码
  • 百度云下载包含MySQL rpm包的required_rpms文件夹(MySQL官网由于国内特殊的网络环境原因,下载很慢,经常中断导致无法安装,因而将安装所需的rpm包单独down了下来,又因为所有MySQL包加起来有200+M,不方便传到Github上,只能传到百度云上单独下载)
  • 将百度云上下载的required_rpms文件夹加入到代码主目录,最终目录如下:
.
├── 0_start.sh
├── 1_shell_init.sh
├── 2_deploy_firewall.sh
├── 3_install_git.sh
├── 4_install_pip.sh
├── 5_install_docker.sh
├── 6_install_mysql.sh
├── 7_enable_sshd.sh
├── 99_shell_finalize.sh
├── deploy_mysql_hook
│   ├── 0_deploy_mysql.sh
│   └── init_mysql.exp
├── post_hook
│   └── 1_test.sh
├── pre_hook
│   └── 1_test.sh
├── README.md
└── required_rpms
    ├── mysql
    │   ├── mariadb-5.5.56-2.el7.x86_64.rpm
    │   ├── perl-5.16.3-292.el7.x86_64.rpm
    │   ├── perl-Carp-1.26-244.el7.noarch.rpm
    │   ├── perl-constant-1.27-2.el7.noarch.rpm
    │   ├── perl-Encode-2.51-7.el7.x86_64.rpm
    │   ├── perl-Exporter-5.68-3.el7.noarch.rpm
    │   ├── perl-File-Path-2.09-2.el7.noarch.rpm
    │   ├── perl-File-Temp-0.23.01-3.el7.noarch.rpm
    │   ├── perl-Filter-1.49-3.el7.x86_64.rpm
    │   ├── perl-Getopt-Long-2.40-2.el7.noarch.rpm
    │   ├── perl-HTTP-Tiny-0.033-3.el7.noarch.rpm
    │   ├── perl-libs-5.16.3-292.el7.x86_64.rpm
    │   ├── perl-macros-5.16.3-292.el7.x86_64.rpm
    │   ├── perl-parent-0.225-244.el7.noarch.rpm
    │   ├── perl-PathTools-3.40-5.el7.x86_64.rpm
    │   ├── perl-Pod-Escapes-1.04-292.el7.noarch.rpm
    │   ├── perl-podlators-2.5.1-3.el7.noarch.rpm
    │   ├── perl-Pod-Perldoc-3.20-4.el7.noarch.rpm
    │   ├── perl-Pod-Simple-3.28-4.el7.noarch.rpm
    │   ├── perl-Pod-Usage-1.63-3.el7.noarch.rpm
    │   ├── perl-Scalar-List-Utils-1.27-248.el7.x86_64.rpm
    │   ├── perl-Socket-2.010-4.el7.x86_64.rpm
    │   ├── perl-Storable-2.45-3.el7.x86_64.rpm
    │   ├── perl-Text-ParseWords-3.29-4.el7.noarch.rpm
    │   ├── perl-threads-1.87-4.el7.x86_64.rpm
    │   ├── perl-threads-shared-1.43-6.el7.x86_64.rpm
    │   ├── perl-Time-HiRes-1.9725-3.el7.x86_64.rpm
    │   └── perl-Time-Local-1.2300-2.el7.noarch.rpm
    ├── mysql57-community-release-el7-11.noarch.rpm
    ├── mysql-community-server
    │   ├── mysql-community-client-5.7.20-1.el7.x86_64.rpm
    │   ├── mysql-community-common-5.7.20-1.el7.x86_64.rpm
    │   ├── mysql-community-devel-5.7.20-1.el7.x86_64.rpm
    │   ├── mysql-community-libs-5.7.20-1.el7.x86_64.rpm
    │   ├── mysql-community-libs-compat-5.7.20-1.el7.x86_64.rpm
    │   └── mysql-community-server-5.7.20-1.el7.x86_64.rpm
    └── mysql-devel
        ├── keyutils-libs-devel-1.5.8-3.el7.x86_64.rpm
        ├── krb5-devel-1.15.1-8.el7.x86_64.rpm
        ├── libcom_err-devel-1.42.9-10.el7.x86_64.rpm
        ├── libkadm5-1.15.1-8.el7.x86_64.rpm
        ├── libselinux-devel-2.5-11.el7.x86_64.rpm
        ├── libsepol-devel-2.5-6.el7.x86_64.rpm
        ├── libverto-devel-0.2.5-4.el7.x86_64.rpm
        ├── mariadb-devel-5.5.56-2.el7.x86_64.rpm
        ├── openssl-devel-1.0.2k-8.el7.x86_64.rpm
        ├── pcre-devel-8.32-17.el7.x86_64.rpm
        └── zlib-devel-1.2.7-17.el7.x86_64.rpm
  • 在主目录运行source 0_start.sh,等待执行完成即可,执行结果如下:
==================================================
shell exec time
1_shell_init.sh      exec time:      0 m  25 s
pre_hook     exec time:      0 m  0 s
2_deploy_firewall.sh     exec time:      0 m  2 s
3_install_git.sh     exec time:      0 m  7 s
4_install_pip.sh     exec time:      4 m  3 s
5_install_docker.sh      exec time:      1 m  2 s
6_install_mysql.sh   exec time:      1 m  6 s
deploy_mysql_hook    exec time:      0 m  3 s
7_enable_sshd.sh     exec time:      0 m  0 s
post_hook    exec time:      0 m  0 s

all_shell        exec time:      6 m  48 s
==================================================

博客更新地址

环境

Ubuntu 16.04 桌面版

安装

使用或调试pyautogui程序需要在有图形界面的系统上,此处使用的是 Ubuntu 16.04 桌面版。

pip install python3–xlib
apt–get install scrot
apt–get install python3–tk
apt–get install python3–dev
pip install pyautogui

鼠标操作

鼠标移动

pyautogui 使用 x-y 坐标系,左上角的坐标是(0, 0)

获取屏幕分辨率

screen_width, screen_height = pyautogui.size()

获取当前鼠标坐标

x, y = pyautogui.position()

使用绝对坐标移动(moveTo)

顺时针移动,画十次方框,duration参数表示持续时间,即当前点到达下一个点所需花费的时间。

for i in range(10):
    pyautogui.moveTo(300, 300, duration=0.25)
    pyautogui.moveTo(400, 300, duration=0.25)
    pyautogui.moveTo(400, 400, duration=0.25)
    pyautogui.moveTo(300, 400, duration=0.25)

画十次圆

screen_width, screen_height = pyautogui.size()
print('screen_width: %s\nscreen_height: %s' % (screen_width, screen_height))

r = 250 # 圆半径
o_x = screen_width / 2 # 圆心X坐标
o_y = screen_height / 2 # 圆心Y坐标
pi = math.pi

for i in range(10):
    for angle in range(0, 360, 20):
# 利用圆的参数方程
        x = o_x + r * math.sin(angle * pi / 180)
        y = o_y + r * math.cos(angle * pi / 180)
pyautogui.moveTo(x, y, duration=0.1)

使用相对坐标移动(moveRel)

for i in range(10):
    pyautogui.moveRel(100, 0, duration=0.25)
    pyautogui.moveRel(0, 100, duration=0.25)
    pyautogui.moveRel(-100, 0, duration=0.25)
    pyautogui.moveRel(0, -100, duration=0.25)

实时获取鼠标位置坐标

try:
    while True:
        x, y = pyautogui.position()
        print(x, y)
except KeyboardInterrupt:
    print('\nExit.')

鼠标点击

pyautogui.click(x=cur_x, y=cur_y, button='left')
  • x, y是要点击的位置,默认是鼠标当前位置
  • button 是要点击的按键,有三个可选值: left, middle, right,默认是left

在当前位置点击右键

pyautogui.click(button='right')

在指定位置点击左键

pyautogui.click(100, 100)

其它函数
* pyautogui.doubleClick(): 双击
* pyautogui.rightClick(): 右击
* pyautogui.middleClick(): 中击

鼠标拖拽

使用绝对坐标拖拽(dragTo)

# 画一个回字
pyautogui.moveTo(300, 300, duration=0.25)
pyautogui.dragTo(400, 300, duration=0.25)
pyautogui.dragTo(400, 400, duration=0.25)
pyautogui.dragTo(300, 400, duration=0.25)
pyautogui.dragTo(300, 300, duration=0.25)

pyautogui.moveTo(200, 200, duration=0.25)
pyautogui.dragTo(500, 200, duration=0.25)
pyautogui.dragTo(500, 500, duration=0.25)
pyautogui.dragTo(200, 500, duration=0.25)
pyautogui.dragTo(200, 200, duration=0.25)

使用相对坐标拖拽(dragRel)

# 画一个回字
pyautogui.moveTo(300, 300, duration=0.25)
pyautogui.dragRel(100, 0, duration=0.25)
pyautogui.dragRel(0, 100, duration=0.25)
pyautogui.dragRel(-100, 0, duration=0.25)
pyautogui.dragRel(0, -100, duration=0.25)

pyautogui.moveTo(200, 200, duration=0.25)
pyautogui.dragRel(300, 0, duration=0.25)
pyautogui.dragRel(0, 300, duration=0.25)
pyautogui.dragRel(-300, 0, duration=0.25)
pyautogui.dragRel(0, -300, duration=0.25)

滚轮

使用函数scroll(),它只接受一个整数,值为正则往上滚,值为负则往下滚。

pyautogui.scroll(200)

根据像素颜色定位按钮位置

img = pyautogui.screenshot()  # 截屏
img_color = img.getpixel((300, 300))  # 获取坐标颜色 (48, 10, 36)
is_matched = pyautogui.pixelMatchesColor(300, 300, (48, 10,36))  # 判断屏幕坐标的像素颜色是不是等于某个值
print(is_matched)  # True

根据按钮图片定位按钮位置

corn_locate = pyautogui.locateOnScreen('corn/settings.png') # 找到按钮所在坐标,分别含义是按钮左上角x坐标,左上角y坐标,x方向大小,y方向大小 (5, 560, 54, 54)
corn_center_x, corn_center_y = pyautogui.center(corn_locate) # 找到按钮中心点
pyautogui.click(corn_center_x, corn_center_y) # 点击按钮

键盘操作

键盘按键

输入普通字符串

pyautogui.typewrite('Hello, world!', 0.25)  # 0.25表示每输完一个字符串延时0.25秒

输入特殊字符

代码 按键
enter/return/\n 回车
esc ESC键
shiftleft/shiftright 左右SHIFT键
altleft/altright 左右ALT键
ctrlleft/ctrlright 左右CTRL键
tab/\t TAB键
backspace/delete BACKSPACE/DELETE键
pageup/pagedown PAGE UP/PAGE DOWN键
home/end HOME/END
up/down/left/right 上下左右键
f1/f2/… F1/F2/…
volumemute/volumedown/volumeup ??
pause PAUSE键
capslock/numlock/scrolllock CAPS LOCK/NUM LOCL/SCROLL LOCK键
insert INSERT键
printscreen PRINT SCREEN键
winleft/winright 左右Win键
command Mac上的command键
pyautogui.click(100, 100)
pyautogui.typewrite('Hello, world!', 0.25)
pyautogui.typewrite(['enter', 'a', 'b', 'left', 'left', 'X', 'Y'], '0.1')

键盘的按下和释放

  • keyDown(): 按下某个键
  • keyUp(): 松开某个键
  • press(): 一次完整的按键,前面两个函数的结合

如关闭某个窗口(ALT + F4)

pyautogui.keyDown('altleft')
pyautogui.press('f4')
pyautogui.keyUp('altleft')

或者直接使用热键函数

pyautogui.hotkey('altleft', 'f4')

博客更新地址

环境

Python 3.6.4

简介

Blinker是一个基于Python的强大的信号库,支持一对一、一对多的订阅发布模式,支持发送任意大小的数据等等,且线程安全。

安装

pip install blinker

使用

signal为单例模式

signal 使用了单例模式,允许代码的不同模块得到相同的signal,而不用互相传参。

In [1]: from blinker import signal

In [2]: a = signal('signal_test')

In [3]: b = signal('signal_test')

In [4]: a is b
Out[4]: True

订阅信号

使用.connect(func)方法来订阅一个信号,当信号发布时,该信号的订阅者会执行func

In [5]: def subscriber(sender):
   ...:     print('Got a signal sent by {}'.format(sender))
   ...:     

In [6]: ready = signal('ready')

In [7]: ready.connect(subscriber)
Out[7]: <function __main__.subscriber(sender)>

发布信号

使用.send()方法来发布信号,会通知所有订阅者,如果没有订阅者则什么都不会发生。

In [12]: class Processor(object):
    ...:     
    ...:     def __init__(self, name):
    ...:         self.name = name
    ...:         
    ...:     def go(self):
    ...:         ready = signal('ready') 
    ...:         ready.send(self)
    ...:         print('Processing...')
    ...:         complete = signal('complete')
    ...:         complete.send(self)
    ...:         
    ...:     def __repr__(self):
    ...:         return '<Processor {}>'.format(self.name)
    ...:     

In [13]: processor_a = Processor('a')

In [14]: processor_a.go()
Got a signal sent by <Processor a>
Processing...

订阅指定的发布者

.connect()方法接收一个可选参数sender,可用于接收指定发布者的信号。

In [18]: def b_subscriber():
    ...:     print('Caught signal from peocessor_b')
    ...:     

In [19]: ready.connect(b_subscriber, sender=processor_b)
Out[19]: <function __main__.b_subscriber(sender)>

In [20]: processor_a.go()
Got a signal sent by <Processor a>
Processing...

In [21]: processor_b.go()
Got a signal sent by <Processor b>
Caught signal from peocessor_b
Processing...

订阅者接收发布者传递的数据

除了之前的通过.connect方法来订阅外,还可以通过装饰器的方法来订阅。
订阅的方法可以接收发布者传递的数据。

In [22]: send_data = signal('send-data')

In [23]: @send_data.connect
    ...: def receive_data(sender, **kw):
    ...:     print('Caught signal from {}, data: {}'.format(sender, kw))
    ...:     return 'received!'
    ...: 
    ...: 

In [24]: result = send_data.send('anonymous', abc=123)
Caught signal from anonymous, data: {'abc': 123}

.send方法的返回值是一个由元组组成的列表,每个元组的第一个值为订阅者的方法,第二个值为订阅者的返回值

In [25]: result
Out[25]: [(<function __main__.receive_data(sender, **kw)>, 'received!')]

匿名信号

信号可以是匿名的,可以使用Signal类来创建唯一的信号(S大写,这个类不像之前的signal,为非单例模式)。
下面的on_readyon_complete为两个不同的信号

In [28]: from blinker import Signal

In [29]: class AltProcessor(object):
    ...:     on_ready = Signal()
    ...:     on_complete = Signal()
    ...:     
    ...:     def __init__(self, name):
    ...:         self.name = name
    ...:     
    ...:     def go(self):
    ...:         self.on_ready.send(self)
    ...:         print('Altername processing')
    ...:         self.on_complete.send(self)
    ...:         
    ...:     def __repr__(self):
    ...:         return '<AltProcessor {}>'.format(self.name)

通过装饰器来订阅

订阅者接收发布者传递的数据中简单地演示了使用装饰器来订阅,但是那种订阅方式不支持订阅指定的发布者,这时候我们可以用.connect_via(sender)

In [31]: @dice_roll.connect_via(1)
    ...: @dice_roll.connect_via(3)
    ...: @dice_roll.connect_via(5)
    ...: def odd_subscriver(sender):
    ...:     print('Observed dice roll {}'.format(sender))
    ...:     

In [32]: result = dice_roll.send(3)
Observed dice roll 3

In [33]: result = dice_roll.send(1)
Observed dice roll 1

In [34]: result = dice_roll.send(5)
Observed dice roll 5

In [35]: result = dice_roll.send(2)

检查信号是否有订阅者

In [37]: bool(signal('ready').receivers)
Out[37]: True

In [38]: bool(signal('complete').receivers)
Out[38]: False

In [39]: bool(AltProcessor.on_complete.receivers)
Out[39]: False

In [40]: signal('ready').has_receivers_for(processor_a)
Out[40]: True

参考

Blinker 官方文档

博客更新地址

环境

CentOS-7-x86_64-Minimal-1708

原理

Alt text

ELK 简介与安装

可以参考我之前的博客:集中式日志系统 ELK 协议栈__简介与安装

安装 filebeat

除了ELK 系统之外,我们需要 filebeat 来读取文件中的内容并发送给 ELK 系统。
(filebeat 实际上和 ELK 是同一家公司的,属于 Beats 类。)

curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.1.1-x86_64.rpm
sudo rpm -vi filebeat-6.1.1-x86_64.rpm
rm filebeat-6.1.1-x86_64.rpm -f

构造数据源

为了进行日志分析的演示,需要动态地去构造访问数据,可以使用我写的一个简单的 Python 脚本,保存文件名为insert_fake_nginx_log.py
Python 脚本中默认 Nginx 的日志路径为/var/log/nginx/access.log,如果你的日志文件不在这个路径,可根据实际情况修改。

# insert_fake_nginx_log.py

import datetime
import random
import time


def append_log_line_to_nginx(line_count):
    curr_time = datetime.datetime.now().strftime('%d/%b/%Y:%H:%M:%S')
    ip_list = [
        '116.199.2.208', '116.199.2.196', '116.199.115.79', '101.53.101.172',
        '47.94.23.128', '222.73.68.144', '120.27.49.85', '182.107.12.28',
        '122.228.179.178', '182.129.240.254', '180.118.128.196', '106.75.25.3',
        '113.140.25.4', '121.40.81.129', '121.31.139.10', '182.129.240.254',
        '122.228.179.178', '117.90.2.225', '121.40.127.145', '120.76.77.152',
        '59.51.121.191', '119.5.0.7', '123.177.20.80', '121.31.101.225',
        '106.75.56.87', '223.241.78.253', '210.77.22.203', '58.56.149.198',
        '125.67.75.53', '223.241.116.7', '121.232.144.131', '111.155.116.226',
        '183.140.83.39', '111.40.84.73', '111.40.84.73', '120.8.232.135',
    ]

    request_list = [
        'GET /tasks/',
        'POST /tasks/'
        'PUT /tasks/26925b7335f0869824469f27b4e3167a/',
        'GET /tasks/26925b7335f0869824469f27b4e3167a/',
        'GET /tasks/26925b7335f0869824469f27b4e3167b/upload/',
        'GET /tasks/26925b7335f0869824469f27b4e3167b/download/',
    ]

    agent_list = [
        'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; '
        'Trident/7.0; .NET4.0C; .NET4.0E)',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
        '(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063',
        'PostmanRuntime/6.4.1',
        'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 '
        '(KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
    ]

    response_list = [
        '200', '401', '403', '404', '500',
    ]

    with open('/var/log/nginx/access.log', 'a') as f:
        for _ in range(line_count):
            f.write('%s - - [%s +0800] "%s HTTP/1.1" %s 25 "-" "%s" "-"\n' % (
                random.choice(ip_list),
                curr_time,
                random.choice(request_list),
                random.choice(response_list),
                random.choice(agent_list))
            )


if __name__ == '__main__':
    print('running...')
    while True:
        append_log_line_to_nginx(random.randint(1, 100))
        time.sleep(random.randint(1, 5))

配置 filebeat

新建配置文件

vi /etc/filebeat/nginx_log_reader.yml

配置文件内容及说明如下

filebeat.prospectors:
- type: log
  paths:
    - /var/log/nginx/access.log  # nginx 访问日志路径
  fields:  # 添加额外字段
    log_type: nginx  # 额外字段,一个键值对
  fields_under_root: true  # 该值默认为 false,即将上面添加的额外字段放在根键"field"下,此处将该值设置为 true,则表示将额外字段直接放在根键中
output.logstash:  # 输出到 Logstash 中
  hosts: ["localhost:5044"]  # Logstash 的 IP 和 Port

启动之前写的 Python 脚本,不断地往 Nginx 日志文件中写入 log

python insert_fake_nginx_log.py

开启 filebeat

cd /usr/share/filebeat/
filebeat -e -c nginx_log_reader.yml -d "publish"

此时 filebeat 将会去尝试连接 Logstash 的端口5044,但我们没有启用 Logstash 的 Beats 插件,所以会显示以下错误信息

2018/01/27 09:19:01.645646 output.go:74: ERR Failed to connect: dial tcp 127.0.0.1:5044: getsockopt: connection refused

配置 Logstash

进入 Logstash 目录

cd /usr/share/logstash

创建配置文件

vi nginx_log.yml

配置文件如下,后面会对配置文件进行详细说明

input {
    beats {
        port => "5044"
    }
}
filter{
    if [log_type] == "nginx" {
        grok {
            match => { "message" => "%{HTTPD_COMBINEDLOG}"}
            remove_field => ["message", "beat"]
        }
        geoip {
            source => "clientip"
        }
        date {
            locale => "en-US"
            match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
            target => "@timestamp"
            timezone => "Asia/Shanghai"
            remove_field => ["timestamp"]
        }
    }
}
output {
    stdout { codec => rubydebug }
    elasticsearch {
        hosts => ["localhost:9200"]
        index => "logstash-nginx_log"
    }
}

Logstash 配置文件说明

Logstash 配置文件基本结构

Logstash 分为三个部分,inputfilteroutput,每一个部分都提供了很多插件,三者之间关系如下,filter可根据需求跳过:
Alt text

配置文件也分为三个部分,其中filter部分可以省略

input {
    ...
}

filter{
    ...
}

output {
    ...
}

配置文件语法

配置文件中,我们可以在filteroutput中使用一些语法。

使用变量

表示顶级字段时,可以这样使用

[fieldname]

表示嵌套字段时,可以这样使用

[top-level field][nested field]
使用条件语句

可以使用if/else if/else 条件语句,也可以嵌套

if EXPRESSION {
    ...
}
else if EXPRESSION {
    ...
}
else {
    ...
}

在条件语句中可以使用的逻辑操作符:
* ==, !=, <, >, <=, >=
* =~, !~
* in, not in
* and, or, nand(与非,都为真则False,其它情况都是True), xor(异或,相同为False,不同为True)
* !
* ()分组

使用系统环境变量

使用环境变量

`${var}`

当环境变量未定义时使用默认值

${var:default_value}

如果在 logstash 启动后,系统的环境变量更新了,则需要去重启 logstash 才能使用到新的环境变量

input

官方输入插件文档导航见:官方文档 – Logstash 输入插件

此处我们使用的插件是beats,一般只需要设置port项以指定传输的端口号即可

input {
    beats {
        port => "5044"
    }
}

filter{
    ...
}

output {
    ...
}

filter

官方解析插件文档导航见:官方文档 – Logstash 解析插件

此处我们使用了三个解析插件: grokgeoipdate

input {
    ...
}
filter{
    if [log_type] == "nginx" {
        grok {
            match => { "message" => "%{HTTPD_COMBINEDLOG}"}
            remove_field => ["message", "beat"]
        }
        geoip {
            source => "clientip"
        }
        date {
            locale => "en-US"
            match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
            target => "@timestamp"
            timezone => "Asia/Shanghai"
            remove_field => ["timestamp"]
        }
    }
}
output {
    ...
}
解析插件之 Grok
        grok {
            match => { "message" => "%{HTTPD_COMBINEDLOG}"}
            remove_field => ["message", "beat"]
        }

Nginx 的日志结构是这样的

180.118.128.196 - - [18/Jan/2018:01:20:31 +0800] "PUT /workers/ HTTP/1.1" 401 25 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E)" "-"

可以看到,这是一堆字符串,而我们需要输出到 Elasticsearch 中的数据结构是 JSON,而 Grok 解析插件就可以将这一堆字符串解析为 JSON 格式。

Grok 解析的原理是将字符串中的字符通过解析模式(本质为正则表达式)提取出来,然后再赋值给一个个变量,从而构造成 JSON。

Github – Grok 内置的120种解析模式

Grok 中解析模式的语法是%{SYNTAX:SEMANTIC}
* SYNTAX是与文本匹配的模式名称,比如180.118.128.196将会被匹配为IP
* SEMANTIC是需要给这段文本匹配的标识符,即变量名。

除此之外,还可以在后面加上你想指定的数据类型,比如%{NUMBER:num:int},目前只支持指定数据格式为intfloat,如不设置则将默认匹配为str

Nginx 的日志格式可以直接使用官方提供的解析模式HTTPD_COMBINEDLOG

180.118.128.196 - - [18/Jan/2018:01:20:31 +0800] "PUT /workers/ HTTP/1.1" 401 25 "-" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E)" "-"
HTTPD_COMMONLOG %{IPORHOST:clientip} %{HTTPDUSER:ident} %{HTTPDUSER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)

HTTPD_COMBINEDLOG %{HTTPD_COMMONLOG} %{QS:referrer} %{QS:agent}
解析插件之 GeoIP
        geoip {
            source => "clientip"
        }

GeoIP 解析插件可以将 IP 地址解析为地理位置,基于 Maxmind GeoLite2 数据库。

下图为经过 GeoIP 解析过的 IP 地址在 Kibana (后面会详细说明)中的展现
Alt text

解析插件之 Date
        date {
            locale => "en-US"
            match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
            target => "@timestamp"
            timezone => "Asia/Shanghai"
            remove_field => ["timestamp"]
        }
locale

字符串类型,区域设置,一般设定为en-US

match

列表类型,要去解析为日期的字段名称及格式。格式有以下几种
* ISO8601 可以解析类似2018-01-29T09:48:01.101Z的格式
* UNIX 可以解析整型和浮点型(带毫秒)的 UNIX 时间,如1326149001/1326149001.132
* UNIX_MS 可以解析整型的毫秒级别的 UNIX 时间,如1366125117000
* TAI64N 可以解析tai64n类型的时间,格式为@4000000052f88ea32489532c,不常见。

除此之外,还可以直接使用 Joda 时间库类处理,格式定义如下:
* 年
yyyy,四位数的年,如2018
yy,两位数的年,如18


  • M,最小位数的月份,如1, 12
    MM,两位数的月份,如01, 12
    MMM,缩写的月份单词,如JanDec
    MMMM,完整的月份单词,如JanuaryDecember


  • d,最小位数的天,如1, 31
    dd,两位数的天,如01, 31

  • 小时
    H,最小位数的小时,如0, 24
    HH,两位数的小时,如00, 24

  • 分钟
    m,最小位数的分钟,如0, 59
    mm,两位数的分钟,如00, 59


  • s,最小位数的秒,如0, 59
    ss,最大位数的秒,如00, 59

  • 毫秒
    S,十分之一秒,如毫秒为012的话,表现为0
    SS,百分之一秒,如毫秒为012的话,表现为01
    SSS,千分之一秒,如毫秒为012的话,表现为012

  • 一年中的第几周
    w,最小位数的周,如152
    ww,两位数的周,如0152

  • 一年中的第几天
    D,最小位数的天,如1, 365

  • 一周中的第几天
    e,最小位数的天,以数字表示,如17
    E/EE/EEE,缩写的周单词,如MonSun
    EEEE,完整的周单词,如MondaySunday

target

字符串类型,如要去解析的字段名,缺省值为@target

timezone

字符串类型,时区,如Asia/Shanghai

tag_on_failure

列表类型,指定如果解析失败的话,添加到tag字段的值,默认为["_dateparsefailure"]
一般不做修改。

output

官方输出插件文档导航见:官方文档 – Logstash 输出插件

此处我们使用了两个输出插件,stoutelasticsearch

input {
    ...
}

filter{
    ...
}

output {
    stdout { codec => rubydebug }
    elasticsearch {
        hosts => ["localhost:9200"]
        index => "logstash-nginx_log"
    }
}

stdout { codec => rubydebug }可以将所有的输出信息打印到终端。
elasticsearch就是把信息输出到 Elasticsearch 中,一般只需要设置两项,hostsindex
* hosts指定了 Elasticsearch 的 IP 和端口号,为列表形式,有多个则可以指定多个;
* index指定了数据输出到 Elasticsearch 后,赋予给 Elasticsearch 的索引值,用以对不同文档进行拆分。建议以logstash-为前缀,否则需要手动地去设定 Elasticsearch 模板。

启动 Logstash

cd /usr/share/logstash
bin/logstash -f nginx_log.yml --config.reload.automatic

--config.reload.automatic的意思是,当配置文档发生修改时,自动重启。

当你启动时,会报以下错误

[ERROR] 2018-01-29 10:29:39.982 [[main]-pipeline-manager] elasticsearch - Failed to install template. {:message=>"Template file '' could not be found!", :class=>"ArgumentError", :backtrace=>["/usr/share/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/template_manager.rb:31:in `read_template_file'", "/usr/share/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/template_manager.rb:17:in `get_template'", "/usr/share/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/template_manager.rb:7:in `install_template'", "/usr/share/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/common.rb:57:in `install_template'", "/usr/share/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-elasticsearch-9.0.2-java/lib/logstash/outputs/elasticsearch/common.rb:26:in `register'", "/usr/share/logstash/logstash-core/lib/logstash/output_delegator_strategies/shared.rb:9:in `register'", "/usr/share/logstash/logstash-core/lib/logstash/output_delegator.rb:43:in `register'", "/usr/share/logstash/logstash-core/lib/logstash/pipeline.rb:343:in `register_plugin'", "/usr/share/logstash/logstash-core/lib/logstash/pipeline.rb:354:in `block in register_plugins'", "org/jruby/RubyArray.java:1734:in `each'", "/usr/share/logstash/logstash-core/lib/logstash/pipeline.rb:354:in `register_plugins'", "/usr/share/logstash/logstash-core/lib/logstash/pipeline.rb:743:in `maybe_setup_out_plugins'", "/usr/share/logstash/logstash-core/lib/logstash/pipeline.rb:364:in `start_workers'", "/usr/share/logstash/logstash-core/lib/logstash/pipeline.rb:288:in `run'", "/usr/share/logstash/logstash-core/lib/logstash/pipeline.rb:248:in `block in start'"]}

这是因为 Elasticsearch 未启动,无法找到对应的模板,等会将 Elasticsearch 启动就好了。

配置 Elasticsearch

按照安装步骤切换到非root账户后,直接启动

cd /usr/share/elasticsearch
bin/elasticsearch

配置 Kibana

按照安装步骤修改host为0.0.0.0后,直接启动

cd /usr/share/kibana
bin/kibana

在 Kibana 中创建索引

  • 打开浏览器,在地址栏输入yourdomain:5601(记得将yourdomain修改为安装了 Kibana 机器的 IP)
    Alt text

  • 进入ManagementIndex Patterns
    Alt text

  • 在创建页面的Index Patterns输入框内输入logstash-nginx_log,即我们之前在 Logstash中创建的索引
    Alt text

  • 点击下一步之后,在Time Filter field name下拉框内选择@timestamp作为时间轴的参数
    Alt text

  • 成功创建索引
    Alt text

查看 Discover 页面

  • 切换到Discover页面,可以看到 nginx 的access.log中的日志已经被解析为一个个键值对了。
    Alt text

  • 展开一个文档,可以看到键值对的明细
    Alt text
    Alt text
    Alt text

  • 在右上角可以选择要显示的时间段,有相对时间段和绝对时间段,也可以在Quick中快速选择
    Alt text

  • 可以设置页面自动刷新,设定每隔一段时间就自动去获取最新的数据
    Alt text

  • 在左上角可以设置筛选字段来对当前数据进行选择,比如只想要响应为200的数据
    Alt text

制作更直观的视图

  • 切换到Visualize,点击Create a visualization,可以看到有很多种表格形式供选择。
    Alt text

    Alt text

  • 首先制作历史访问量视图,选择Area
    Alt text

  • 选择数据来源的索引,即我们之前创建好的logstash-nginx_log
    Alt text

  • 首先对数据来源的log_type进行筛选,虽然我们现在只有一个log_type,即nginx
    Alt text

  • 选择 Y 轴要展示的数据,此处我们要统计访问量,就选择默认的Count,并重命名为PV,然后点击上方的箭头使之生效
    Alt text

  • 然后选择 X 轴要展示的数据,此处我们需要访问历史数据,则需要选择时间来作为参数
    Alt text

  • 点击上方的蓝色箭头,就可以在右侧看到历史访问量的视图了
    Alt text

  • 点击右上角的Save,即可将视图保存下来,以备后面放入Dashborad中
    Alt text

  • 其它视图操作类似,不作赘述,仅贴出具体配置截图

  • PV Total(Metric)
    Alt text

  • UV History(Line)
    Alt text

  • UV Total(Metric)
    Alt text

  • IP Location(Coordinate Map)
    Alt text

  • Agent Pie(Pie)
    Alt text

  • Response Count(Horizontal Bar)
    Alt text

制作仪表板

  • 切换到Dashboard,点击Create a dashboard,点击Add以添加视图
    Alt text

    Alt text

  • 将我们刚才创建的视图全部添加进仪表板
    Alt text

  • 将它们的大小和位置进行调整
    Alt text

  • 在右上角选择要显示的时间范围
    Alt text

  • 选择自动刷新的时间间隔
    Alt text

  • 可以在选项中选择隐藏视图标题,以让仪表板更一体化
    Alt text

  • 保存时,勾选Store time with dashboard以保存刚才的时间设置
    Alt text

  • 点击右上角的Full screen,可以让仪表板全屏,可以再按下F11,使浏览器全屏,最终效果如下图
    Alt text

扩展

博客更新地址

环境

CentOS-7-x86_64-Minimal-1708

简介

集中式日志系统 ELK 并不是一款软件,而是一整套解决方案,是三个软件的首字母缩写:Elasticsearch,Logstash 和 Kibana。
这三款软件都是开源软件,通常是配合使用,而且又先后归于 Elastic.co 公司名下,故被简称为 ELK 协议栈。

基本原理
Alt text

Elasticsearch

简介

Elasticsearch 是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。它是一个建立在全文搜索引擎 Apache Lucene 基础上的搜索引擎,使用 Java 语言编写。

安装

  • 安装 Java
yum install java -y
  • 添加镜像仓库
cat << "EOF" >> /etc/yum.repos.d/elasticsearch.repo
[elasticsearch-6.x]
name=Elasticsearch repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
EOF
  • 安装 Elasticsearch
yum install elasticsearch -y

启动

Elasticsearch 不允许以 root 用户的身份启动,必须切换到普通用户,并在切换之前修改相关目录权限。

  • 建立普通用户账号(或使用已有的普通用户账号)
useradd elsearch
  • 修改相关目录权限
chown -R elsearch /etc/sysconfig/elasticsearch
chown -R elsearch /etc/elasticsearch
chown -R elsearch /var/log/elasticsearch/
chown -R elsearch /var/lib/elasticsearch/
  • 切换账号
su elsearch
  • 切换到运行目录
cd /usr/share/elasticsearch/
  • 启动 Elasticsearch
bin/elasticsearch
  • 看到命令行输出以下信息时,说明已经启动成功
[2018-01-27T15:20:32,252][INFO ][o.e.n.Node               ] [_izbz0j] started

Hello World

  • 打开另一个终端,输入curl 'https://localhost:9200/?pretty',会得到以下响应
[root@localhost elasticsearch]# curl 'https://localhost:9200/?pretty'
{
  "name" : "_izbz0j",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "UNE-x9AiT0mDS0upgBOkcA",
  "version" : {
    "number" : "6.1.2",
    "build_hash" : "5b1fea5",
    "build_date" : "2018-01-10T02:35:59.208Z",
    "build_snapshot" : false,
    "lucene_version" : "7.1.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

Logstash

简介

Logstash 是一个具有实时渠道能力的数据收集引擎。使用 JRuby 语言编写。其作者是世界著名的运维工程师乔丹西塞 ( JordanSissel )。

安装

  • 安装 java (此前已安装的可以跳过此步骤)
yum install java -y
  • 添加镜像仓库
cat << "EOF" >> /etc/yum.repos.d/logstash.repo
[logstash-6.x]
name=Elastic repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
EOF
  • 安装 Logstash
yum install logstash -y

启动

  • 切换到 Logstash 目录
cd /usr/share/logstash/
  • 启动
bin/logstash -e 'input { stdin { } } output { stdout {} }'

Hello World

启动后,当命令行显示The stdin plugin is now waiting for input:时,输入Hello World,然后就会直接在命令行输出

The stdin plugin is now waiting for input:
Hello World
2018-01-27T07:40:43.301Z localhost.localdomain Hello World

Kibana

简介

Kibana 是一款基于 Apache 开源协议,使用 JavaScript 语言编写,为 Elasticsearch 提供分析和可视化的 Web 平台。它可以在 Elasticsearch 的索引中查找,交互数据,并生成各种维度的表图。

安装

Kinaba 需要和 Elasticsearch 结合起来运行,必须将两者的版本号保持一致(比如 Kinaba 6.1.0 和 Elasticsearch 6.1.0),或是 Elasticsearch 的版本号稍高(比如 Kinaba 6.0.0 和 Elasticsearch 6.1.0)。
从 Kibana 6.0.0 开始,仅支持64位操作系统。

  • 添加镜像仓库
cat << "EOF" >> /etc/yum.repos.d/kibana.repo
[kibana-6.x]
name=Kibana repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
EOF
  • 安装 Kibana
yum install kibana -y

启动

  • 修改配置文件/etc/kibana/kibana.yml,将其中的#server.host: "localhost"改为server.host: 0.0.0.0(此步是为了在其它有图形化界面的机器能够访问到本机上的 Kibana,安装了图形化界面 CentOS 的可忽略此步)

  • 切换到 Kibana 目录

cd /usr/share/kibana/
  • 启动 Kibana
bin/kibana
  • 成功启动会显示以下信息
[root@localhost kibana]# bin/kibana
  log   [07:56:34.537] [info][status][plugin:kibana@6.1.2] Status changed from uninitialized to green - Ready
  log   [07:56:34.614] [info][status][plugin:elasticsearch@6.1.2] Status changed from uninitialized to yellow - Waiting for Elasticsearch
  log   [07:56:34.637] [info][status][plugin:console@6.1.2] Status changed from uninitialized to green - Ready
  log   [07:56:34.662] [info][status][plugin:metrics@6.1.2] Status changed from uninitialized to green - Ready
  log   [07:56:34.916] [info][status][plugin:timelion@6.1.2] Status changed from uninitialized to green - Ready
  log   [07:56:34.921] [info][listening] Server running at https://0.0.0.0:5601
  log   [07:56:35.363] [info][status][plugin:elasticsearch@6.1.2] Status changed from yellow to green - Ready

Hello World

  • Kibana 运行在端口5601上,CentOS 默认的防火墙策略是禁用了该端口的,需要手动开放端口
systemctl start firewalld
firewall-cmd --set-default-zone=public && \
firewall-cmd --zone=public --add-port=5601/tcp --permanent && \
firewall-cmd --reload
  • 打开浏览器,在地址栏输入yourdomain:5601(记得将yourdomain修改为安装了 Kibana 机器的 IP)
    Alt text

More

集中式日志系统 ELK 协议栈 – Nginx 日志分析实战

博客更新地址

环境

以下环境仅代表本文测试环境,其它版本应该也可以。
* 虚拟机 Linux: CentOS Linux release 7.4.1708 (Core)
* 物理机 Windows: Windows 10 1709
* 移动端 Android: 7.1.1

CentOS 7 网络配置

虚拟机配置(在物理机 Windows 10 中)

需将网络连接方式改为桥接模式,否则手机端无法连接到虚拟机中的CentOS,Vmware和VirtualBox都有这个功能,不赘述。

查看物理机网络配置(在物理机 Windows 10 中)

右键开始菜单,打开Windows PowerShell(此处为无线连接,有线连接可以去查看有线网卡)

PS C:\Users\smy13> ipconfig
...

无线局域网适配器 WLAN:

   连接特定的 DNS 后缀 . . . . . . . : DHCP HOST
   本地链接 IPv6 地址. . . . . . . . : fe80::b441:8184:1b12:d4fb%4
   IPv4 地址 . . . . . . . . . . . . : 192.168.0.111
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : 192.168.0.1

...

记住以上的子网掩码默认网关,以下会用到

设置静态IP(在CentOS 中)

打开/etc/sysconfig/network-scripts/ifcfg-ens33,最后的ifcfg-ens33为网卡名,不一定是这个名字,一般都以ifcfg-开头。

vi /etc/sysconfig/network-scripts/ifcfg-ens33

将配置文件改为如下(重点为最后六行,说明见注释)

TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
# BOOTPROTO=dhcp  # 默认IP获取方式为DHCP,需注释掉
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
IPV6INIT=yes
IPV6_AUTOCONF=yes
IPV6_DEFROUTE=yes
IPV6_FAILURE_FATAL=no
IPV6_ADDR_GEN_MODE=stable-privacy
NAME=ens33
UUID=227a8a72-2a97-468c-91c2-d0851ddec08e
DEVICE=ens33
ONBOOT=yes

# 添加以下内容
BOOTPROTO=static  # 设置IP获取方式为静态
IPADDR=192.168.0.200  # 你想设置的静态IP
NETMASK=255.255.255.0  # 刚在物理机中看到的子网掩码
GATEWAY=192.168.0.1  # 刚在物理机中看到的默认网关
DNS1=192.168.0.1  # 设置为默认网关
DNS2=8.8.8.8  # 勿动

重启网络

systemctl restart network

可以通过ping www.baidu.com查看是否配置成功

[root@localhost ~]# ping www.baidu.com
PING www.a.shifen.com (115.239.211.112) 56(84) bytes of data.
64 bytes from www.baidu.com (115.239.211.112): icmp_seq=1 ttl=54 time=12.1 ms
64 bytes from www.baidu.com (115.239.211.112): icmp_seq=2 ttl=54 time=8.19 ms
64 bytes from www.baidu.com (115.239.211.112): icmp_seq=3 ttl=54 time=9.76 ms
64 bytes from www.baidu.com (115.239.211.112): icmp_seq=4 ttl=54 time=12.3 ms

防火墙配置

配置firewalld

firewall-cmd --permanent --zone=public --add-service=samba
firewall-cmd --reload

关闭selinux

setenforce 0

打开selinux配置文件

vi /etc/selinux/config

将配置文件中的SELINUX=enforcing改为SELINUX=disabled以禁止selinux开机自启

samba 配置

安装 samba 包及依赖

安装 samba 包

yum install samba -y

开启samba服务并设置为开机自启

systemctl start smb
systemctl enable smb

配置共享文件夹

建立共享目录,(/samba/haha/为将来要共享的文件夹,可自定义)

mkdir /samba/haha/ -p

修改目录用户组及权限

chown -R nobody:nobody /samba/haha/
chmod -R 755 /samba/haha/

修改 samba 配置文件

修改配置文件

vi /etc/samba/smb.conf

配置文件如下(说明见注释)

[global]  # 公共配置
        workgroup = WORKGROUP  # 工作组,建议和宿主机保持一致
        server string = Samba Server Version %v  # samba服务器名称,就这样设置就行,%v表示版本号
        max connections = 0  # 最大连接数,0表示不限制
        log file = /var/log/samba/log.%m  # 日志文件存放路径,%m表示连接设备名
        security = user  # 设置Samba Server共享目录只能被授权的用户访问
        passdb backend = smbpasswd  # 使用smb自己的工具smbpasswd来给系统用户(真实用户或者虚拟用户)设置一个Samba密码
        encrypt passwords = yes  # 密码加密
        smb passwd file = /etc/samba/smbpasswd  # smbpasswd文件的目录,有些版本的samba可能需要手动创建该文件
        # 以下四行为设置在共享目录创建文件和文件夹的默认权限
        create mask = 0644
        force create mode = 0644
        directory mask = 0755
        force directory mode = 0755

[haha]  # 私有配置,haha这个名字可自定义
        comment = haha  # 共享名,任意字符串
        path = /samba/haha  # 共享目录路径
        writable = yes  # 指定该共享路径为可写

添加账户到 samba 账户中,并配置单独的密码

[root@localhost ~]# smbpasswd -a root
New SMB password:
Retype new SMB password:

重启samba服务

systemctl restart smb

拷贝物理机中的视频

打开文件资源管理器,在地址栏输入\\192.168.0.200(192.168.0.200为 CentOS 中设置的静态IP),就可以看到我们配置的haha共享目录了,将视频文件复制进去即可。

手机端操作

  • 安装ES文件浏览器和MX 播放器,酷安和豌豆荚都有。

  • 安装完之后打开ES文件浏览器,点击左上角的菜单栏中的”我的网络”
    1

  • 点击右上角菜单-新建-局域网,服务器名为在 CentOS 中设置的静态IP,用户名和密码为刚才执行smbpasswd -a root的用户(root)和密码
    2

  • 之后点进去就可以看到我们刚才存在samba服务器中的视频了,选择使用MX播放器播放即可在线播放,加载速度由局域网决定,一般电影都足够了。
    3

博客更新地址

Schemas是一种机器可读的文档,描述了API的端点,它们的URLs和所支持的操作。
Schemas可以用于自动生成文档,也可以用来驱动可以与API交互的动态客户端库。

Core API

为了提供Schemas支持,REST框架使用了Core API。
安装coreapi

pip install coreapi

添加schema

REST框架既支持自定义Schemas视图,也支持自动生成Schemas视图。
由于我们使用的是viewset和route,我们可以简单地自动生成Schemas视图。

现在我们需要通过在URL中配置一个自动生成的Schemas视图,为我们的API添加一个Schemas。
编辑urls.py

from rest_framework.schemas import get_schema_view

schema_view = get_schema_view(title='Pastebin API')

urlpatterns = [
    url(r'^schema/$', schema_view),
    ...
]

我们通过命令行的形式访问该接口,并指定接收格式为corejson

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# http https://127.0.0.1:80/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Length: 1498
Content-Type: application/coreapi+json
Date: Fri, 01 Dec 2017 12:04:53 GMT
Server: WSGIServer/0.2 CPython/3.6.3
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "_meta": {
        "title": "Pastebin API",
        "url": "https://127.0.0.1/schema/"
    },
    "_type": "document",
    "snippets": {
        "highlight": {
            "_type": "link",
            "action": "get",
...

使用命令行客户端

既然我们的API提供了一个Schemas url,我们可以使用一个动态的客户端来与API进行交互。
为了演示,我们使用Core API客户端。
安装coreapi-cli

pip install coreapi-cli

使用命令行客户端访问schema接口

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi get https://127.0.0.1:80/schema/
<Pastebin API "https://127.0.0.1/schema/">
    snippets: {
        list()
        read(id)
        highlight(id)
    }
    users: {
        list()
        read(id)
    }

由于我们没有登录,所以我们现在只能看到只读的接口。

列出现有的所有snippets

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi action snippets list
[
    {
        "url": "https://127.0.0.1/snippets/1/",
        "id": 1,
        "highlight": "https://127.0.0.1/snippets/1/highlight/",
        "owner": "song",
        "title": "test_1",
        "code": "print('hello world')",
        "linenos": true,
        "language": "python",
        "style": "friendly"
    },
]

有些API需要一些命名参数

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "https://www.w3.org/TR/html4/strict.dtd">

<html>
<head>
  <title>test_1</title>
  <meta http-equiv="content-type" content="text/html; charset=None">
  <style type="text/css">
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
pre { line-height: 125%; }
...

登录

需要写入的接口需要我们登录才能做,通过以下命令进行登录(test:qazxswedc为用户名和密码)

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi credentials add 127.0.0.1 test:qazxswedc --auth basic
Added credentials
127.0.0.1 "Basic dGVzdDpxYXp4c3dlZGM="

重载

(django_rest_framework) [root@iZuf62kvdczytcyqlvr2pgZ django_rest_framework]# coreapi reload
Pastebin API "https://127.0.0.1:80/schema/">
    snippets: {
        create(code, [title], [linenos], [language], [style])
        delete(id)
        highlight(id)
        list()
        partial_update(id, [title], [code], [linenos], [language], [style])
        read(id)
        update(id, code, [title], [linenos], [language], [style])
    }
    users: {
        list()
        read(id)
    }

我们现在可以对数据进行写操作了

coreapi action snippets delete --param id=1

关于

本人是初学Django REST framework,Django REST framework 学习纪要系列文章是我从官网文档学习后的初步消化成果,如有错误,欢迎指正。

学习用代码Github仓库:shelmingsong/django_rest_framework

本文参考的官网文档:Tutorial 7: Schemas & client libraries

博客更新地址