Linux – CentOS 7网络配置

安装完CentOS 7时,网络是通的。此时网络配置如下:

网络配置文件路径:

/etc/sysconfig/network-scripts/ifcfg-enp0s3

其中“enp03”是你的网卡名称。

TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="NO"
BOOTPROTO="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="enp0s3"
UUID="1538d24b-770f-44ee-83af-e1da305dfabc"
DEVICE="enp0s3"
ONBOOT="yes"

此时可以ping通百度,wget也没问题。

后续使用中,发现网络断了—可以ping通同网段的IP,但是ping不通百度,wget一直提示无法解析目标主机。

修改网络配置文件如下图:

未分类

重启网络:

service network restart

仍旧不行。在VM主界面-设备-网络-网络中,将连接方式从“网络地址转换(NAT)”切换成“桥接网卡”。重启网络,正常!

未分类

【FileZilla传文件】

由于上面网络配置文件中分配了固定IP,那么就可以使用FileZilla传文件到VM里面的CentOS 7中。

未分类

CentOS 7 系统配置Apache

现在公司的项目由于一直是一个外包团队在维护,一直运行在Windows Server上,我接手之后从长远考虑以及熟练及安全、性能等方面考虑,我最终决定换成Linux,而由于运行的是公司的正式项目,毫无疑问选择了CentOS,至于版本,我选择了最新的CentOS7。而服务器本打算是用流行的Nginx的,但是几个CGI参数始终无法配置好,出于稳定性考虑,最后选择了Apache。

安装基本软件包

由于CentOS 7 自带的PHP版本才5.4,不能满足我们开发的程序的要求,本着能包管理器安装就不编译安装的原则,选择了webstatic这个源的包,更新系统:

yum update

安装EPEL源:

yum install epel-release

安装webstatic源:

rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm

安装Apache、安装PHP及Mariadb数据库命令如下:

yum install httpd php56w php56w-mysql php56w-pdo php56w-gd php56w-mcrypt php56w-mbstring php56w-json php56w-xml php56w-openssl mariadb-server -y

对,你没看错,CentOS下的Apache在包管理器中的名字就是httpd。这样就安装好了各个所需要的组件,接下来就是配置了。

Apache基本配置

用 yum 安装的apache,配置文件在 /etc/httpd/ ,我们需要改的各个配置文件都在这个目录下面。

首先,更改/etc/httpd/conf/httpd.conf中的ServerName字段,否则会提示AH00558字段:

[root@localhost ~]# httpd -t
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using localhost.localdomain. Set the 'ServerName' directive globally to suppress this message
Syntax OK

接着,在/etc/httpd/conf.d/下面新建一个example.conf文件,这个就是我们需要的虚拟主机配置文件,如果只是想要HTTP服务,复制下面这段并进行相应修改即可:

<VirtualHost *:80>
ServerName www.example.com
DocumentRoot /var/www/example/
ServerAlias example
ErrorLog /var/log/httpd/example.error.log
CustomLog /var/log/httpd/example.log combined
<Directory /var/www/example>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride FileInfo Options
        Order allow,deny
        allow from all
</Directory>
Header unset X-Powered-By
</VirtualHost>

配置完成之后,用apache自带的工具检查是否有语法错误。

配置Apache提供HTTPS服务

但是由于现在的潮流是提供HTTPS服务,我们这个项目也给App端提供数据接口服务,按照苹果的要求以及处于安全性考虑,确实有必要上HTTPS服务,但是考虑到实际业务情况,我们现阶段不打算强制跳转到HTTPS访问,于是采用的是HTTP和HTTPS都可访问策略,这就要求要准备一份SSL证书了,申请证书这一步按下不表,很多途径都可以申请,我这里只记录下配置步骤。

要使用yum包管理器安装的Apache支持SSL,还需要安装一个Apache模块,命令如下:

yum install mod_ssl

然后是增加对应的SSL配置,在上面那个配置文件末尾增加或者新建一个配置文件都行,相应配置:

<VirtualHost *:443>
ServerName https://www.example.com
DocumentRoot /var/www/example/
ServerAlias example
ErrorLog /var/log/httpd/example.error.log
CustomLog /var/log/httpd/example.log combined
<Directory /var/www/example>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride FileInfo Options
        Order allow,deny
        allow from all
</Directory>
SSLEngine on
SSLCertificateFile /var/www/example-ssl/xxx.pem
SSLCertificateKeyFile /var/www/example-ssl/xxx.key
</VirtualHost>

复制,更改对应的项即可。

然后再次检查一下语法是否有误:

httpd -t

如果没问题,可以重启httpd服务:

systemctl restart httpd

额外配置

上面有个配置项我这里记录一下,Header unset X-Powered-By这一行是让Apache隐藏对应的http header,处于安全考虑不允许后端暴露太多信息。

同样需要隐藏的还有Apache的版本号以及操作系统信息,在/etc/httpd/conf/httpd.conf文件末尾增加下面两行:

ServerTokens Prod
ServerSignature Off

最后再检查一下是否有语法错误,如果一切正常则可以重启Apache服务或者重新加载配置文件。

由于Apache是以apache用户组下的apache用户运行的,所以,对应的网站目录文件需要给予对应的权限才能使网站正常运行:

chown -R apache.apache /var/www/example

上面的文件路径改成对应的网站文件路径即可。

Ansible部署模块的时候出现中文乱码的问题

今天在部署服务的时候遇到了一个很罕见的现象,线上有15台服务器是手机推送消息的服务,新来的小运维使用ansible批量跑部署脚本的时候,发现手机端接收到来的消息全是乱码,然后登陆到服务器,查看日志发现,日志里面就是乱码,如图:

未分类

由于这个问题用户是有感知的,所以属于“事故”级别了,于是小boss大怒,叫运维赶快回滚,然后让开发赶紧重新检查代码,然后开骂测试都是吃屎的么这么大的一个问题都看不出来真是一群猪伤不起啊。

开发看了半天自己的代码,发现没有任何问题,战战兢兢跑来跟新来的小运维窃窃私语,结果我发现这个模块用手动单独部署,日志却是正常的,中文显示十分OK。

未分类

这一下开发就腰杆硬了,说这不是我的锅啊我是无辜的啊老子天天辛苦加班没有功劳也有苦劳没有苦劳也有疲劳老子的代码经得住考验这一切就是部署的问题。

于是我就查看了一下ansible的配置文件,#vim /etc/ansible/ansible.cfg,发现了问题所在:

未分类

这里最后三行需要改成下面的样子,这样就解决了乱码问题。

#module_lang    = C
#module_set_locale = False
module_lang    = zh_CN.UTF-8
module_set_locale = True

精心汇总,史上最全-Ansible运维自动化工具19个常用模块使用实例(root用户角度)

本文章是站在root用户角度对Ansible的19个常用模块进行测试使用。为什么突出是站在root用户角度,因为很多时候,处于服务器安全考虑,我们是禁止root用户直接登录系统,如果要远程登录或者远程控制服务器,必须先登录普通用户,再登录root用户,因此在这个过程中,Ansible中root用户想使用相应的模块则没有像普通用户那么简单。

一、模块列表

1、setup
2、ping
3、file
4、copy
5、command
6、shell
7、script
8、cron
9、yum
10、service
11、group
12、user
13、stat
14、mount
15、fetch
16、synchronize
17、get_url
18、hostname
19、wait_for

二、模块示例

1、setup 功能:搜集系统信息

通过命令获取所有的系统信息,搜集主机的所有系统信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup

未分类

注意:/etc/ansible/.hosts 文件的写法很关键,官网也出了相关的说明,只不过官方的文档分版本描述的,稍不注意就掉坑了,具体格式如下:

[xxxx-root]
192.168.1.51 ansible_become=True ansible_become_method=su ansible_become_user=root ansible_become_pass=1111111111
192.168.1.52 ansible_become=True ansible_become_method=su ansible_become_user=root ansible_become_pass=1111111111

搜集系统信息并以主机名为文件名分别保存在/tmp/facts 目录

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup --tree /tmp/facts

未分类

搜集和内存相关的信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup -a 'filter=ansible_*_mb'

未分类

搜集网卡信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup -a 'filter=ansible_eth[0-2]'

未分类

2、ping 功能:测试网络连通性, ping模块没有参数

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m ping

未分类

3、file 功能:文件属性设置

  • force:需要在两种情况下强制创建软链接,一种是源文件不存在,但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no

  • group:定义文件/目录的属组

  • mode:定义文件/目录的权限

  • owner:定义文件/目录的属主

  • path:必选项,定义文件/目录的路径

  • recurse:递归设置文件的属性,只对目录有效

  • src:被链接的源文件路径,只应用于state=link的情况

  • dest:被链接到的路径,只应用于state=link的情况

state包括以下:

  • directory:如果目录不存在,就创建目录

  • file:即使文件不存在,也不会被创建

  • link:创建软链接

  • hard:创建硬链接

  • touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间

  • absent:删除目录、文件或者取消链接文件

创建软链接:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m file -a "src=/etc/resolv.conf dest=/tmp/resolv.conf state=link"

未分类

删除软连接:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m file -a "path=/tmp/resolv.conf state=absent"

未分类

创建目录(文件):

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m file -a "path=/root/testfile group=ihavecar owner=ihavecar mode=700 state=directory"

未分类

4、copy 功能:复制文件到远程主机

  • backup: #在覆盖之前,将源文件备份,备份文件包含时间信息。有两个选项:yes|no

  • content: #用于替代“src”,可以直接设定指定文件的值

  • dest: #必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录

  • directory_mode: #递归设定目录的权限,默认为系统默认权限

  • force: #如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes

  • others:#所有的file模块里的选项都可以在这里使用

  • group # 复制到远程主机后,指定文件或目录的属

  • mode # 复制到远程主机后,指定文件或目录权限,类似与 `chmod’指明如 0644

  • owner # 复制到远程主机后,指定文件或目录属主

  • src:被复制到远程主机的本地文件,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。在这种情况下,如果路径使用“/”来结尾,则只复制目录里的内容,如果没有用“/”来结尾,则包含目录在内的整个内容全部复制,类似于rsync。

将本地文件“/etc/ansible/ansible.cfg”复制到远程服务器,设置属主和属组及文件权限

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m copy -a "src=/etc/ansible/ansible.cfg dest=/tmp/ansible.cfg owner=ihavecar group=root mode=400"

未分类

5、command 功能:在远程主机上执行命令

Command不适用于有shell变量的情况,也不适用于有管道符或者&&的情况,如果要使用此种情况,那么可以使用shell模块

相关选项如下:

  • creates:一个文件名,当该文件存在,则该命令不执行

  • free_form:要执行的linux指令

  • chdir:在执行指令之前,先切换到该目录

  • removes:一个文件名,当该文件不存在,则该选项不执行

  • executable:切换shell来执行指令,该执行路径必须是一个绝对路径

远程执行查询系统负载:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m command -a "uptime"

未分类

如带有管道的,则会如下报错,command是不允许有管道的:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m command -a "uptime | grep 192.168.1.50"

未分类

6、shell 功能:切换到某个shell执行指定的指令

切换到某个shell执行指定的指令,与command不同的是,此模块可以支持命令管道,同时还有另一个模块也具备此功能:raw

  • chdir # 执行之前,先cd到指定目录在执行命令

  • creates # 一个文件名,当这个文件存在,则该命令不执行

  • executable # 切换shell来执行命令,需要使用命令的绝对路径

  • free_form= # 执行的命令

  • removes # 一个文件名,这个文件不存在,则该命令不执行

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m shell -a "cat /etc/passwd | grep root"

未分类

可远程执行脚本:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m shell -a "sh /tmp/echo.sh"

未分类

7、script 功能:指定本地的脚本文件,到远程主机运行一次

注意这个模块和shell模块的不同,shell模块是要求客户端上有这个脚本才能执行;script是要求ansible服务端有这个脚本就可以了,执行的时候是不会拷贝这个脚本到客户端的。

  • creates # 一个文件名,当这个文件存在,则该命令不执行

  • free_form= # 本地脚本路径

  • removes # 一个文件名,这个文件不存在,则该命令不执行

ansible端的已有脚本,就只有一条/sbin/ifconfig 命令,该脚本被拿到客户端上执行,并返回结果:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m script -a "/etc/ansible/masterecho.sh"

未分类

8、cron 功能:计划任务管理

  • backup # 如果设置,创建一个crontab备份

  • cron_file # 如果指定, 使用这个文件cron.d,而不是单个用户crontab

  • day # 日应该运行的工作( 1-31, *, */2, etc )

  • hour # 小时 ( 0-23, *, */2, etc )

  • job # 指明运行的命令是什么

  • minute # 分钟( 0-59, *, */2, etc )

  • month # 月( 1-12, *, */2, etc )

  • name # 定时任务描述

  • reboot # 任务在重启时运行,不建议使用,建议使用special_time

  • special_time # 特殊的时间范围,参数:reboot(重启时),annually(每年),monthly(每月),weekly(每周),daily(每天),hourly(每小时)

  • state # 指定状态,prsent表示添加定时任务,也是默认设置,absent表示删除定时任务

  • user # 以哪个用户的身份执行

  • weekday # 周 ( 0-6 for Sunday-Saturday, *, etc )

定义一个时间任务,每隔3分钟执行一次时间记录任务:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m cron -a 'name="ntp datatime job" minute=*/3 hour=* day=* month=* weekday=* job="echo `date` >> /tmp/linshidata.txt"'

未分类

效果如下:

未分类

删除一个时间任务:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m cron -a 'name="ntp datatime job" minute=*/3 hour=* day=* month=* weekday=* job="echo `date` >> /tmp/linshidata.txt" state=absent'

未分类

9、yum 功能:软件包安装管理

  • conf_file # yum的配置文件

  • disable_gpg_check # 关闭gpg_check

  • disablerepo # 不启用某个源

  • enablerepo # 启用某个源

  • List # 非幂等性命令

  • name= # 指定要安装的包,如果有多个版本需要指定版本,否则安装最新的包

  • state # 安装(present’),安装最新版(latest’),卸载程序包(`absent’)

指定版本安装包:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=httpd-devel-2.2.15 state=present"

未分类

指定安装最新版本的包:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=httpd-devel-2.2.15 state=latest"

未分类

指定rpm包来安装:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=/usr/local/src/kel.noarch.rpm state=present"

指定远程网址rpm包来进行安装:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6.0.el6.ngx.noarch.rpm state=present"

删除包:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=httpd-devel-2.2.15 state=absent"

未分类

10、service 功能:系统服务管理

  • arguments # 向服务传递的命令行参数

  • enabled # 设置服务开机自动启动,参数为yes|no

  • name= # 控制服务的名称

  • pattern # 如果通过status指令来查看服务的状态时,没有响应,就会通过ps指令在进程中根据该模式进行查找,如果匹配到,则认为该服务依然在运行

  • runlevel # 设置服务自启动级别

  • sleep # 如果执行了restarted,则在stop和start之间沉睡几秒钟

  • state # 启动started’ 关闭stopped’ 重新启动 restarted’ 重载reloaded’

设置httpd服务为开机自启动模式,并限制开启httpd服务

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m service -a "name=httpd state=started enabled=yes"

重启httpd服务,中间sleep 10秒钟

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m service -a "name=httpd state=restarted sleep=10"

未分类

11、group 功能:系统用户组管理

  • gid # 设置组的GID号

  • name= # 管理组的名称

  • state # 指定组状态,默认为创建,设置值为absent为删除

  • system # 设置值为yes,表示为创建系统组

创建一个foo组,指定gid号

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m group -a "name=foo gid=360 system=no"

未分类

删除一个组:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m group -a "name=foo gid=360 system=no state=absent"

未分类

12、user 功能:系统用户账号管理

  • comment # 用户的描述信息

  • createhome # 是否创建家目录

  • force # 在使用state=absent’时, 行为与userdel –force’一致.

  • group # 指定基本组

  • groups # 指定附加组,如果指定为(‘groups=’)表示删除所有组

  • home # 指定用户家目录

  • login_class # 可以设置用户的登录类 FreeBSD, OpenBSD and NetBSD系统.

  • move_home # 如果设置为`home=’时, 试图将用户主目录移动到指定的目录

  • name= # 指定用户名

  • non_unique # 该选项允许改变非唯一的用户ID值

  • password # 指定用户密码

  • remove # 在使用 state=absent’时, 行为是与userdel –remove’一致.

  • shell # 指定默认shell

  • state #设置帐号状态,不指定为创建,指定值为absent表示删除

  • system # 当创建一个用户,设置这个用户是系统用户。这个设置不能更改现有用户。

  • uid #指定用户的uid

  • update_password # 更新用户密码

添加用户foo,指定密码,设置家目录,不允许远程登录

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m user -a "name=foo password=123456 home=/home/foo shell=/sbin/nologin"

未分类

彻底删除一个用户,包括家目录:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m user -a "name=foo remove=yes state=absent"

13、stat 功能:获取远程文件信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m stat -a "path=/etc/passwd"

未分类

14、mount 功能:配置挂载点

  • fstype:必选项,挂载文件的类型

  • name:必选项,挂载点

  • opts:传递给mount命令的参数

  • src:必选项,要挂载的文件

  • state:必选项

  • present:只处理fstab中的配置

  • absent:删除挂载点

  • mounted:自动创建挂载点并挂载之

  • umounted:卸载

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m mount -a "name=/mnt src=/dev/sda5 fstype=ext4 opts=rostate=present"

15、fetch 功能:文件拉取模块,主要是将远程主机中的文件拷贝到本机中

  • 和copy模块的作用刚刚相反,并且在保存的时候使用hostname来进行保存,当文件不存在的时候,会出现错误,除非设置了选项fail_on_missing为yes

  • Dest:用来存放文件的目录,例如存放目录为backup,源文件名称为/etc/profile在主机pythonserver中,那么保存为/backup/pythonserver/etc/profile

  • Fail_on_missing: Yes/no,当源文件不存在的时候,标识为失败
    Flat: 允许覆盖默认行为从hostname/path到/file的,如果dest以/结尾,它将使用源文件的基础名称

  • Src: 在远程拉取的文件,并且必须是一个file,不能是目录

  • Validate_checksum Yes/no,当文件fetch之后进行md5检查

从远程机器上床送文件到ansible服务器

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m fetch -a "src=/root/123 dest=/root"

未分类

src表示为远程主机上需要传送的文件的路径,dest表示为本机上的路径,在传送过来的文件,是按照IP地址或hostname进行分类,然后路径是源文件的路径,例如上面的最终路径为/root/192.168.1.50/root/123在拉取文件的时候,必须拉取的是文件,不能拉取文件夹

指定路径目录进行保存:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m fetch -a "src=/root/Ssh.py dest=/root/kel/ flat=yes"

在使用参数为flat的时候,如果dest的后缀名为/,那么就会保存在目录中,然后直接保存为文件名,如上例的结果为 dest”: “/root/kel/Ssh.py;

当dest后缀不为/的时候,那么就会直接保存为kel的文件,如上例1所示。主要是在于dest是否已/结尾,从而来区分这是个目录还是路径。

16、synchronize 功能:将主控方/root/a目录推送到指定节点的/tmp目录下

选项说明

  • archive # 是否采用归档模式同步,即以源文件相同属性同步到目标地址

  • checksum # 是否效验

  • compress # 开启压缩,默认为开启

  • copy_links # 同步的时候是否复制连接

  • delete # 删除源中没有而目标存在的文件(即以推送方为主)

  • dest= # 目标地址

  • dest_port # 目标接受的端口,ansible配置文件中的 ansible_ssh_port 变量优先级高于该 dest_port 变量

  • dirs # 以非递归的方式传输目录

  • existing_only # Skip creating new files on receiver.

  • group # Preserve group

  • links # Copy symlinks as symlinks.

  • mode # 模式,rsync 同步的方式 PUSHPULL,默认都是推送push。如果你在使用拉取pull功能的时候,可以参考如下来实现mode=pull 更改推送模式为拉取模式

  • recursive # 是否递归 yes/no

  • rsync_opts # 使用rsync 的参数

  • rsync_path # 服务的路径,指定 rsync 命令来在远程服务器上运行。这个参考rsync命令的–rsync-path参数,–rsync-path=PATH # 指定远程服务器上的rsync命令所在路径信息

  • rsync_timeout # 指定 rsync 操作的 IP 超时时间,和rsync命令的 –timeout 参数效果一样.

  • set_remote_user # put user@ for the remote paths. If you have a custom ssh config to define the remote user for

  • src=‘#‘” # 源,同步的数据源

  • times #

  • –exclude=.Git 忽略同步.git结尾的文件

由于模块默认启用了archive参数,该参数默认开启了recursive, links, perms, times, owner,group和-D参数。如果你将该参数设置为no,那么你将停止很多参数,比如会导致如下目的递归失败,导致无法拉取

压缩传输a目录到被控节点:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m synchronize -a 'src=/root/a dest=/tmp/ compress=yes'

注意:主控端的a目录传输到被控节点,文件属性过去被控节点后,只能是remote user的权限,也就是ihavecar的,不能是root的,也不允许传输到ihavecar这个用户无权限的目录下去,不然会报错。

未分类

使用pull模式拉取文件到主控节点:

由于模块,默认都是推送push。因此,如果你在使用拉取pull功能的时候,可以参考如下来实现

mode=pull 更改推送模式为拉取模式。

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m synchronize -a 'mode=pull src=/tmp/a dest=/root/ compress=yes'

注意:从被控节点传目录回到主控节点,可以遵循权限,被控节点端目录的属性是什么,传回主控节点的目录权限就是什么,并不会像push方式那样属性改变成普通用户的。还有一点需要注意的是,从被控节点传输回主控节点,只有一个节点能传输成功,其他的节点都会失败。当然,如果各个被控节点的文件如果不是一样的话,那么各个节点都会传文件回来。那样主控端就会收到各个节点的全部文件。

未分类

17、get_url 功能:将某个url的文件下载到被控节点的某个位置

将simplejson-3.8.2.tar.gz 下载到/tmp/下:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m get_url -a "url=https://pypi.python.org/packages/source/s/simplejson/simplejson-3.8.2.tar.gz dest=/tmp/"

未分类

18、hostname 功能:主要用来修改主机的名称

在查看的时候,主要查看文件/etc/sysconfig/network,重启之后才能生效

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m hostname -a "name=ansible-test"

未分类

19、wait_for 功能:等待一个事件发生或处理完后,再继续运行下面的事情

  • connect_timeout 默认5秒,在下一个事情发生前等待链接的时间,单位是秒

  • delay 延时,大家都懂,在做下一个事情前延时多少秒

  • host 默认127.0.0.1,执行这个模块的host

  • path 当一个文件存在于文件系统中,下一步才继续。

  • port 端口号,如8080

  • state 默认started,对象是端口的时候start状态会确保端口是打开的,stoped状态会确认端口是关闭的

  • present 对象是文件的时候present或者started会确认文件是存在的,而absent会确认文件是不存在的。

  • started

  • stopped

  • absent

10秒后在当前主机开始检查8000端口,直到端口启动后返回:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m wait_for -a "port=22612 delay=10"

未分类

检查path=/tmp/foo直到文件存在后继续:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m wait_for -a "path=/tmp/foo"

未分类

确认/var/lock/file.lock不存在继续:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m wait_for -a "path=/var/look/file.lock state=absent"

未分类

Logstash的容器化与Ansible多环境下单配置文件发布

原因

为了发布、迁移方便,最近决定将公司项目中用的logstash容器化,最初原打算沿用原多进程方案,在容器内通过supervisor启动多个不同配置进程,但使用官方容器时并支持启动多个实例。

最终解决方式:将集群的配置文件组装在一起,不同的path对应不同的filter与output;由于不同主机上组件不同,日志也不同。因此在ansible发布时通过脚本检测生成log file 的path列表,适配集群上的不同组件。这样的好处是只需要维护一份配置,操作简单。

官方logstash镜像

在dockerhub上搜索了到一个即将被废弃的镜像:https://hub.docker.com/_/logstash/。
最新版镜像在es官方进行维护: https://www.elastic.co/guide/en/logstash/current/docker.html
由于直接使用原版镜像时映射到容器内部的pipeline.conf一直提示No permission,明明都已经设置成755,宿主机上并没有logstash这一用户,chown不方便。所以决定对官方镜像稍加修改,再推送到公司内的dockerhub私有docker registry上。

FROM docker.elastic.co/logstash/logstash:5.6.1
MAINTAINER CodingCrush
ENV LANG en_US.UTF-8
# Disable ES monitoring in the official image
ENV XPACK_MONITORING_ENABLED false
# TimeZone: Asia/Shanghai
ENV TZ Asia/Shanghai
ENV PIPELINE_WORKERS 5
# Default user is logstash
USER root

这个Dockerfile很简单,主要设置了User为root,解决conf文件的权限不足问题。另外关闭容器自带的es xpack检查功能,设置时区与pipeline的 worker数量。
然后打个tag,推到私有docker registry上。

docker build -t dockerhub.xxx.net/logstash:5.6.1
docker push dockerhub.xxx.net/logstash:5.6.1

容器便修改好了,在生产集群上可pull。

pipeline.conf

这个简化的配置文件中path应该是一个列表,但目前用了一个占位字符串,发布时动态检测通过sed进行更换,实际项目中的组件配置更多,这里写4个意思一下。
值得注意的是: 通过if else 对path进行匹配时如果出现/,需要进行转义,否则ruby进行正则匹配时会报错。

input {
    file {
        path => LOG_FILES_PLACEHOLDER
        type => "nginx"
        start_position => "beginning"
        sincedb_path => "/data/projects/logstash/data/logstash.db"
        codec=>plain{charset=>"UTF-8"}
    }
}
filter {
    if [path] =~ "component1/logs/access" {
        grok & mutate
    } else if [path] =~ "component1/logs/error" {
        grok & mutate
    } else if [path] =~ "component2/logs/access" {
        grok & mutate
    } else if [path] =~ "component2/logs/error" {
        grok & mutate
    }
}
output {
    if [path] =~ "component1/logs/access" {
        stdout{codec=>rubydebug}
    }  else if [path] =~ "component1/logs/error" {
        stdout{codec=>rubydebug}
    } else if [path] =~ "component2/logs/access.log" {
        stdout{codec=>rubydebug}
    } else if [path] =~ "component2/logs/error" {
        stdout{codec=>rubydebug}
    }
}

日志文件检测脚本 detect_logfiles.py

检测脚本同样很简单,用os.path.exists进行过滤。往标准输出打一个列表的string。
此处注意,需要用re.escape进行转义,因为后面还需要用sed进行替换。

import os
import sys
import re
paths = [
    "/data/projects/component1/logs/access.log*",
    "/data/projects/component1/logs/error.log",
    "/data/projects/component2/logs/access.log*",
    "/data/projects/component2/logs/error.log*",
    ......
]
available_paths = [path for path in paths if os.path.exists(path.replace("*", ""))]
sys.stdout.write(re.escape(str(available_paths)))

ansible剧本

在发布时通过sed替换占位串,之所以大费周章搞这么个方式,主要因为我们环境比较复杂,将来横向拓展时组件布局不定,多配置文件维护起来挺麻烦。
为什么用shell?而不用ansible的module? 因为我不会,还懒得去学ansible发明的DSL

- name: Modify lostash.conf
  args:
    chdir: "/data/projects/logstash/"
  shell: 'sed -i "s|LOG_FILES_PLACEHOLDER|$(python detect_logfiles.py)|" logstash.conf'
  become: true
  become_method: sudo
- name: Modify docker-compose.yml
  args:
    chdir: "/data/projects/logstash/"
  shell: 'sed -i "s|HOST_PLACEHOLDER|{{ host }}|" docker-compose.yml'
  become: true
  become_method: sudo

docker-compose

挂载整个文件卷,HOST_PLACEHOLDER占位符是因为我希望通过container名字直观看到组件所在的主机名,而避免生产服务器上误操作。

version: '2'
services:
  logstash:
    image: dockerhub.xxx.net/logstash:5.6.1
    volumes:
       - /etc/localtime:/etc/localtime:ro
       - /data/projects/logstash/hosts:/etc/hosts
       - /data/projects:/data/projects
    container_name: HOST_PLACEHOLDER.logstash
    command: logstash -f /data/projects/logstash/logstash.conf --path.data=/data/projects/logstash/data

启动的command时指定工作目录,将运行状态持久化到外部存储上,方便迁移。

超快的文件搜索工具Ag

前言

Ag 是类似ack, grep的工具, 它来在文件中搜索相应关键字。
官方列出了几点选择它的理由:

  • 它比ack还要快 (和grep不在一个数量级上)
  • 它会忽略.gitignore和.hgignore中的匹配文件
  • 如果有你想忽略的文件,你需要将(congh .min.js cough*)加入到.ignore文件中
  • 它的命令名称更短:-)

安装

下载源码

下载地址: http://geoff.greer.fm/ag

安装PCRE

目前已经有PCRE2,但这里需要PCRE

https://downloads.sourceforge.net/pcre/pcre-8.41.tar.bz2

从官网下载.tar.gz的版本,注意不要下载zip版本

下载后解压缩正常安装

./configure --prefix=/usr                    
            --docdir=/usr/share/doc/pcre-8.41 
            --enable-unicode-properties      
            --enable-pcre16                  
            --enable-pcre32                  
            --enable-pcregrep-libz            
            --enable-pcregrep-libbz2          
            --enable-pcretest-libreadline    
            --disable-static                &&
make && make install

默认是安装到/usr/local下

安装lzma

yum install xz-libs.x86_64 xz-devel.x86_64

安装Ag

./configure PCRE_CFLAGS="-I /usr/local/include" PCRE_LIBS="-L /usr/local/lib -lpcre" && make && make install

命令

Usage: ag [FILE-TYPE] [OPTIONS] PATTERN [PATH]

Recursively search for PATTERN in PATH.
Like grep or ack, but faster.

Example:
ag -i foo /bar/

Output Options:
--ackmate Print results in AckMate-parseable format
-A --after [LINES] Print lines after match (Default: 2)
-B --before [LINES] Print lines before match (Default: 2)
--[no]break Print newlines between matches in different files
(Enabled by default)
-c --count Only print the number of matches in each file.
(This often differs from the number of matching lines)
--[no]color Print color codes in results (Enabled by default)
--color-line-number Color codes for line numbers (Default: 1;33)
--color-match Color codes for result match numbers (Default: 30;43)
--color-path Color codes for path names (Default: 1;32)
--column Print column numbers in results
--[no]filename Print file names (Enabled unless searching a single file)
-H --[no]heading Print file names before each file's matches
(Enabled by default)
-C --context [LINES] Print lines before and after matches (Default: 2)
--[no]group Same as --[no]break --[no]heading
-g --filename-pattern PATTERN
Print filenames matching PATTERN
-l --files-with-matches Only print filenames that contain matches
(don't print the matching lines)
-L --files-without-matches
Only print filenames that don't contain matches
--print-all-files Print headings for all files searched, even those that
don't contain matches
--[no]numbers Print line numbers. Default is to omit line numbers
when searching streams
-o --only-matching Prints only the matching part of the lines
--print-long-lines Print matches on very long lines (Default: >2k characters)
--passthrough When searching a stream, print all lines even if they
don't match
--silent Suppress all log messages, including errors
--stats Print stats (files scanned, time taken, etc.)
--stats-only Print stats and nothing else.
(Same as --count when searching a single file)
--vimgrep Print results like vim's :vimgrep /pattern/g would
(it reports every match on the line)
-0 --null --print0 Separate filenames with null (for 'xargs -0')

Search Options:
-a --all-types Search all files (doesn't include hidden files
or patterns from ignore files)
-D --debug Ridiculous debugging (probably not useful)
--depth NUM Search up to NUM directories deep (Default: 25)
-f --follow Follow symlinks
-F --fixed-strings Alias for --literal for compatibility with grep
-G --file-search-regex PATTERN Limit search to filenames matching PATTERN
--hidden Search hidden files (obeys .*ignore files)
-i --ignore-case Match case insensitively
--ignore PATTERN Ignore files/directories matching PATTERN
(literal file/directory names also allowed)
--ignore-dir NAME Alias for --ignore for compatibility with ack.
-m --max-count NUM Skip the rest of a file after NUM matches (Default: 10,000)
--one-device Don't follow links to other devices.
-p --path-to-ignore STRING
Use .ignore file at STRING
-Q --literal Don't parse PATTERN as a regular expression
-s --case-sensitive Match case sensitively
-S --smart-case Match case insensitively unless PATTERN contains
uppercase characters (Enabled by default)
--search-binary Search binary files for matches
-t --all-text Search all text files (doesn't include hidden files)
-u --unrestricted Search all files (ignore .ignore, .gitignore, etc.;
searches binary and hidden files as well)
-U --skip-vcs-ignores Ignore VCS ignore files
(.gitignore, .hgignore; still obey .ignore)
-v --invert-match
-w --word-regexp Only match whole words
-W --width NUM Truncate match lines after NUM characters
-z --search-zip Search contents of compressed (e.g., gzip) files

File Types:
The search can be restricted to certain types of files. Example:
ag --html needle
- Searches for 'needle' in files with suffix .htm, .html, .shtml or .xhtml.

For a list of supported file types run:
ag --list-file-types

ag was originally created by Geoff Greer. More information (and the latest release)
can be found at http://geoff.greer.fm/ag

自动安装脚本示例

#!/usr/bin/env bash
#Author: Harris Zhu
#Dep: make sure you have the root permission
#Usage . install_ag.sh
set -x
TEMP_DIR=$(mktemp -d Leslie.Guan.XXXXXX)
cd ${TEMP_DIR}
wget https://github.com/ggreer/the_silver_searcher/archive/master.zip
TAR_DIR=$(unzip *.zip)
TAR_DIR=${TAR_DIR%%/*}
TAR_DIR=${TAR_DIR##*:}
cd ${TAR_DIR}
apt-get install -y automake pkg-config libpcre3-dev zlib1g-dev liblzma-dev --force-yes
./build.sh && make install
cd ../../
rm -rf ${TEMP_DIR}
ag -V
set +x

pattern

示例一

未分类

由上面例子可以知道ag的pattern支持s, w等正则

后言

ag的使用非常简单,它的选项也不多,所以我在上面列出了它的help内容。

Redis 乐观锁

乐观锁大多是以数据版本号来进行成功或者失败!

举个例子:

假设某个文章的点赞数为100,此时的version我们暂定没有异常为100.

当用户A对他进行点赞的时候进行操作,那此时的点赞数为100+1、version=101,提交更新时,由于版本号大于数据库记录的版本号,数据被更新,此时数据记录的version=101。

然而在特殊情况下用户B是和用户A是同时进行操作的,也就是说,他获得的version也是100、点赞数为100,提交结果是点赞数为100+1、version=101,但是此时对数据库的版本发现当前数据记录的version也为101,不满足当前提交版本号大于数据库版本号,所以此时更新操作被驳回。

执行实验代码:

WATCH test

value = GET test

value = value+1

MULTI

SET test value

EXEC

由于WATCH 的key会被监视,会校验这个key是否被更改,如果该监视的key 在EXEC执行前被修改了,那么整个事务都会被驳回。

php实现代码

$redis = new redis();  
$redis->connect('127.0.0.1', 6379);  
//获取当前点赞数 
$test = $redis->get("test");  
$count = 1;   //默认每次点赞+1  

$redis->watch("test");  
$redis->multi();  
//设置延迟,方便测试效果。  
sleep(5);  
//插入抢购数据  
$redis->set("test",$test+$count);  
$res = $redis->exec();  
if($res){  
    $new_test = $redis->get("test");  
    echo "点赞成功当前点赞数为:".$new_test."<br/>";  
}else{  
    echo "点赞失败";exit;  
}  

需要注意的是如果使用同一个浏览器的多个标签页同时访问同一个URL,那么浏览器认为这些不同的请求是同一个人,会对你的每个请求进行排队,不做并发处理。不管Nginx还是Apache,都是在并发处理,只不过你的浏览器自作主张,把你的请求阻塞了。

Redis分布式锁解决抢购问题

废话不多说,首先分享一个业务场景-抢购。一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次都去数据库查询显然是不合适的,因此把库存信息存入Redis中,利用redis的锁机制来控制并发访问,是一个不错的解决方案。

首先是一段业务代码:

@Transactional
public void orderProductMockDiffUser(String productId){
    //1.查库存
    int stockNum  = stock.get(productId);
    if(stocknum == 0){
        throw new SellException(ProductStatusEnum.STOCK_EMPTY);
        //这里抛出的异常要是运行时异常,否则无法进行数据回滚,这也是spring中比较基础的   
    }else{
        //2.下单
        orders.put(KeyUtil.genUniqueKey(),productId);//生成随机用户id模拟高并发
        sotckNum = stockNum-1;
        try{
            Thread.sleep(100);
        } catch (InterruptedExcption e){
            e.printStackTrace();
        }
        stock.put(productId,stockNum);
    }
}

这里有一种比较简单的解决方案,就是synchronized关键字。

public synchronized void orderProductMockDiffUser(String productId)

这就是java自带的一种锁机制,简单的对函数加锁和释放锁。但问题是这个实在是太慢了,感兴趣的可以可以写个接口用apache ab压测一下。

ab -n 500 -c 100 http://localhost:8080/xxxxxxx

下面就是redis分布式锁的解决方法。首先要了解两个redis指令
SETNX 和 GETSET,可以在redis中文网上找到详细的介绍。
SETNX就是set if not exist的缩写,如果不存在就返回保存value并返回1,如果存在就返回0。
GETSET其实就是两个指令GET和SET,首先会GET到当前key的值并返回,然后在设置当前Key为要设置Value。

首先我们先新建一个RedisLock类:

@Slf4j
@Component
public class RedisService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    /***
     * 加锁
     * @param key
     * @param value 当前时间+超时时间
     * @return 锁住返回true
     */
    public boolean lock(String key,String value){
        if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){//setNX 返回boolean
            return true;
        }
        //如果锁超时 ***
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue)<System.currentTimeMillis()){
            //获取上一个锁的时间
            String oldvalue  = stringRedisTemplate.opsForValue().getAndSet(key,value);
            if(!StringUtils.isEmpty(oldvalue)&&oldvalue.equals(currentValue)){
                return true;
            }
        }
        return false;
    }
    /***
     * 解锁
     * @param key
     * @param value
     * @return
     */
    public void unlock(String key,String value){
        try {
            String currentValue = stringRedisTemplate.opsForValue().get(key);
            if(!StringUtils.isEmpty(currentValue)&&currentValue.equals(value)){
                stringRedisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("解锁异常");
        }
    }
}

这个项目是springboot的项目。首先要加入redis的pom依赖,该类只有两个功能,加锁和解锁,解锁比较简单,就是删除当前key的键值对。我们主要来说一说加锁这个功能。

首先,锁的value值是当前时间加上过期时间的时间戳,Long类型。首先看到用setiFAbsent方法也就是对应的SETNX,在没有线程获得锁的情况下可以直接拿到锁,并返回true也就是加锁,最后没有获得锁的线程会返回false。 最重要的是中间对于锁超时的处理,如果没有这段代码,当秒杀方法发生异常的时候,后续的线程都无法得到锁,也就陷入了一个死锁的情况。我们可以假设CurrentValue为A,并且在执行过程中抛出了异常,这时进入了两个value为B的线程来争夺这个锁,也就是走到了注释*的地方。currentValue==A,这时某一个线程执行到了getAndSet(key,value)函数(某一时刻一定只有一个线程执行这个方法,其他要等待)。这时oldvalue也就是之前的value等于A,在方法执行过后,oldvalue会被设置为当前的value也就是B。这时继续执行,由于oldValue==currentValue所以该线程获取到锁。而另一个线程获取的oldvalue是B,而currentValue是A,所以他就获取不到锁啦。多线程还是有些乱的,需要好好想一想。

接下来就是在业务代码中加锁啦:首要要@Autowired注入刚刚RedisLock类,不要忘记对这个类加一个@Component注解否则无法注入

private static final int TIMEOUT= 10*1000;
@Transactional
public void orderProductMockDiffUser(String productId){
     long time = System.currentTimeMillions()+TIMEOUT;
   if(!redislock.lock(productId,String.valueOf(time)){
    throw new SellException(101,"换个姿势再试试")
    }
    //1.查库存
    int stockNum  = stock.get(productId);
    if(stocknum == 0){
        throw new SellException(ProductStatusEnum.STOCK_EMPTY);
        //这里抛出的异常要是运行时异常,否则无法进行数据回滚,这也是spring中比较基础的   
    }else{
        //2.下单
        orders.put(KeyUtil.genUniqueKey(),productId);//生成随机用户id模拟高并发
        sotckNum = stockNum-1;
        try{
            Thread.sleep(100);
        } catch (InterruptedExcption e){
            e.printStackTrace();
        }
        stock.put(productId,stockNum);
    }
    redisLock.unlock(productId,String.valueOf(time));
}

大功告成了!比synchronized快了不知道多少倍,再也不会被老板骂了!

CENTOS 7搭建GIT服务器

Centos 下构建私有git服务器

以下操作都是root账户

安装

第一步,安装git服务

yun install -y git

第二步,新建git用户

useradd git

第三步,禁止git用户,shell登录

修改/etc/passwd

git:x:1010:1010:,,,:/home/git:/bin/bash  改为  git:x:1010:1010:,,,:/home/git:/usr/bin/git/git-shell

第四步,创建证书登录

使用命令ssh-keygen -t rsa -C “[email protected]生成公钥,Windows可以通过git bash执行命令,然后找到id_rsa.pub把文件内容导入 /home/git/.ssh/authorized_keys,如果没有文件

cd /home/git

mkdir .ssh

touch .ssh/authorized_keys

第五步,初始化git仓库

在/home/git 下新建仓库目录

mkdir repository

新建仓库,赋予权限

git init --bare test.git

chown -R git:git test.git

第六步,克隆仓库

git clone git@server:/home/git/repository/test.git  # server 可以是域名也可以是ip看配置
Cloning into 'test'...
warning: You appear to have cloned an empty repository.

遇到的问题

上面的都是理想状态下的流程

端口问题

git默认是22端口,如果服务器有修改端口,执行上面的clone会报错

$ git clone [email protected]:/home/git/repository/test.git
Cloning into 'test'...
ssh: connect to host 111.111.111.111 port 22: Connection timed out
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.

正确的方式

git clone ssh://[email protected]:1111/home/git/repository/test.git

权限问题

Cloning into 'xigoubao'...
fatal: '/home/git/repository/test.git' does not appear to be a git repository
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.

如果路径写错了,或者test.git拥有者和组不是git,会报这个错误

示例,路径写错

git clone ssh://[email protected]:1111/home/git/respository/test.git  # repository 拼错

git clone ssh://[email protected]:1111/repository/test.git   # 路径必须为相对于git用户的home目录路径  /home/git/repository/test.git

权限的话,执行命令就可以了

chown -R git.git test.git

git commit 时自动对所有 php 文件执行语法错误检查

使用 Shell 编写 hooks 下的 pre-commit 钩子,实现在 git commit 时检查所有的 .php 文件(忽略所有删除状态的文件)是否存在语法错误,如果存在错误,则终止提交,并输出相关错误信息。

#!/bin/bash

# @auth 后三排
# @site https://housanpai.com

# 错误消息内容
IS_ERROR_MESSAGE=()

while read st file
do

    # 文件状态为 D 时跳出本次循环
    if [ 'D' == "$st" ]
    then
        echo $file
        continue
    fi

    # 文件末为不是 .php 时输出文件,并跳出本次循环
    if [[ ! "$file"  =~ (.php$) ]]
    then
        echo $file
        continue
    fi

    PHP_LINT=`php -l $file`

    # 本文件不存在语法错误,输出结果,并跳出本次循环
    if [ 0 -eq $? ]
    then
        echo $PHP_LINT
        continue
    fi

    # 统计错误消息内容的数据个数
    ERROR_COUNT=${#IS_ERROR_MESSAGE[@]}

    # 将错误的存放到数组里面
    IS_ERROR_MESSAGE[${ERROR_COUNT}]=$PHP_LINT

done <<EOF
`git diff --cached --name-status`
EOF

if [ -n "${IS_ERROR_MESSAGE}" ]
then

    # 循环输出错误消息,并且指定文字颜色为红色
    for ((i=0;i<${#IS_ERROR_MESSAGE[@]};i++))
    do
        echo -e "33[31m ${IS_ERROR_MESSAGE[$i]} 33[0m"
    done

    exit 1

fi

exit 0