数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

今天学习了四种文件共享的方法,分别是vsftp,tftp,samba和nfs。
一、vsftp服务
三大不安全协议:telnet,ftp,http,所有内容均以明文传输。
FTP的工作模式分为两种:
(1)主动模式: FTP服务器主动向客户端发起FTP连接请求。一般用在防火墙限制了FTP流入流量情况下。
(2)被动模式: FTP服务器等待客户端发起连接请求,也是FTP的默认工作模式。
vsftp:very secure ftp daemon,非常安全的FTP守护进行。
Linux系统默认新建文件权限为644,则umask为133;新建目录权限为755,则umask为022。
名称 描述
vsftpd 服务名/软件包名,yum install vsftpd
/etc/vsftpd/vsftpd.conf 主配置文件
匿名用户权限配置
默认FTP路径:
/var/ftp
/var/ftp/pub目录,可读写
anonymous_enable=YES 是否开启匿名用户访问
anon_umask=022 匿名用户创建/上传文件权限
anon_upload_enable=YES 是否允许匿名用户上传文件
anon_mkdir_write_enable=YES 是否允许匿名用户创建文件
anon_other_write_enable=YES 是否允许匿名用户删除、重命名、剪切、修改文件
本地用户权限配置
默认FTP路径:
对应用户的家目录
/home/用户名
虚拟用户权限配置
默认FTP路径:
对应映射用户的家目录
local_enable=YES 是否开启本地用户访问
write_enable=YES 是否允许本地用户上传写入文件
local_umask=022 本地用户上传/创建文件权限
guest_enable=YES 是否开启虚拟用户访问
guest_username=vftp 虚拟用户映射本地用户
allow_writeable_chroot=YES 允许对禁锢的FTP根目录执行写入操作,而不拒绝用户登录
user_config_dir=/etc/vsftpd/vusers_dir 指定虚拟用户权限配置文件目录路径
1、匿名用户配置
第一步:修改vsftpd服务主配置文件,允许匿名用户访问等权限后,重启服务,并将服务加入到开机进程中。
第二步:修改匿名用户默认可读写FTP/pub目录权限。可修改权限为757或修改目录所有者所属组为ftp。
第三步:使用getsebool命令查看与FTP相关的SELinux域策略,并使用setsebool -P命令开启ftpd_full_access=on,-P参数永久立即生效。
第四步:使用匿名用户名anonymous和空密码登录FTP。FTP命令参数:mkdir新建目录;rename重命名文件;rmdir删除目录。
2、本地用户配置
无需配置,注意ftpusers和user_list两个文件中是否存在需要登录FTP的用户,如有需要删除。
ftpusers文件中的用户名一直生效,禁止文件中的用户登录FTP。
user_list文件中的用户名既可以是黑名单也可以是白名单,需要配合userlist_deny=YES | NO参数使用。
3、虚拟用户配置
第一步:新建虚拟用户清单文件,如vuser.list。单数行为账户名,偶数行为密码。
第二步:使用db_load命令加密虚拟用户清单文件。如:db_load -T -t hash -f vuser.list vuser.db,-T:加密;-t:加密类型;-f:需要加密的文件和新生成的文件。
第三步:创建虚拟用户用来映射的本地用户vuser,如:useradd -d /var/vuserftp -s /sbin/nologin vuser,-d:vuser家目录路径也就是虚拟用户FTP路径;-s:禁止虚拟用户登录系统。
第四步:新建支持虚拟用户的PAM文件。
[root @ root Desktop]# cat /etc/pam.d/vftp
auth required pam_userdb.so db=/etc/vsftpd/vuser---加密后的虚拟用户清单文件
account required pam_userdb.so db=/etc/vsftpd/vuser---加密后的虚拟用户清单文件
第五步:修改vsftpd服务主配置文件,并修改pam模块调用的文件为新建PAM文件。如:pam_service_name=vftp
第六步:由于没有专门的虚拟用户FTP权限设置,所以需要使用匿名用户FTP权限进行设置。新建虚拟用户权限配置文件目录,并分别新建各个FTP虚拟用户。若只分配查看权限,则只需要新建对应用户名文件即可。若要读写等权限,需要在以用户名新建的文件中使用匿名用户权限配置命令,配置此虚拟用户权限。最后修改主配置文件,指定虚拟用户权限使用此权限文件目录。如下:
[root @ root Desktop]# ls -lh /etc/vsftpd/vusers_dir/
total 4.0K
-rw-r--r--. 1 root root 0 Oct 1 16:55 dd
-rw-r--r--. 1 root root 79 Oct 1 16:55 ww
[root @ root Desktop]#
[root @ root Desktop]# cat /etc/vsftpd/vusers_dir/ww
anon_upload_enable=YES
anon_mkdir_write_enable=YES
anon_other_write_enable=YES
[root @ root Desktop]#
[root @ root Desktop]# cat /etc/vsftpd/vusers_dir/dd
[root @ root Desktop]#
注:若使用虚拟用户配置,则主配置文件中不能存在匿名用户权限配置,否则虚拟用户权限配置会失效。
二、tftp服务
tftp(Trivial File Transfer Protocol),基于UDP 69号端口的简单文件传输协议。明文、无认证。
名称 描述
tftp-server 服务端软件名,yum install tftp-server
tftp 客户端软件名,yum install tftp
xinetd 服务名,systemcrl restart xinetd
/etc/xinetd.d/tftp 配置文件
disable = yes 将配置文件中的disable改为no即可
server_args = -s /var/lib/tftpboot -c tftp文件目录,若要支持上传put操作,需增加-c参数,并修改/var/lib/tftpboot目录权限为757
get操作
put操作
通过tftp下载/var/lib/tftpboot中的文件到执行命令的当前目录
通过tftp上传执行命令的当前目录中的文件到/var/lib/tftpboot目录
三、samba文件共享服务——主要用于Linux和Windows等其他系统之间文件共享
第一步:安装samba服务。yum install samba。
第二步:配置samba服务主配置文件。vim /etc/samba/smb.conf
[global] --全局配置
workgroup = MYGROUP --确认工作组名,考试题
server string = Samba Server Version %v --显示samba版本
log file = /var/log/samba/log.%m --记录日志文件
max log size = 50 --日志文件大小50KB
security = user --验证方式,默认用户user
passdb backend = tdbsam --用户后台验证类型,默认通过数据库文件验证
load printers = yes --是否共享打印机设备
cups options = raw --打印机配置选项
[Derek-SMB_Share] --SMB共享标识,挂载时需用到
comment = Derek SMB_Share --SMB描述信息
path = /smb --SMB共享目录
public = yes --是否“所有人可见”
writable = yes --是否允许写入操作
第三步:将系统本地已存在用户设置为SMB账号,密码不共享。pdbedit -a -u derek,-a:新建smb账号,-u:指定本地账户,回车后创建smb密码。
第四步:创建smb本地共享目录,修改共享目录所有者所属组为smb账号,并配置本地共享目录SELinux上下文安全策略。 mkdir /home/database --创建smb共享目录 chown -Rf linuxprobe:linuxprobe /home/database --修改smb共享目录所有者所属组为smb账号 emanage fcontext -a -t samba_share_t /home/database --修改smb共享目录SELinux上下文策略为 samba_share_t角色 restorecon -Rv /home/database --重置smb共享目录selinux策略
第五步:设置SELinux安全域策略,允许samba服务访问普通用户家目录。 setsebool -P samba_enable_home_dirs on
第六步:重启smb服务,并将smb服务加入到开机进程中。
第七步:清空并保存iptables防火墙配置。iptables -F;service iptables save
第八步:windows系统即可通过//10.10.78.20访问linux系统共享的smb资源。
第九步:Linux系统需安装cifs-utils服务,并配置smb用户名和密码后即可挂载smb共享目录。
yum install cifs-utils --安装cifs-untils服务
vim auth.smb --新建linux客户端访问SMB的用户名和密码文件
username=derek --用户名
password=derek --密码
domain=MYGROUP --smb共享组
chmod 600 auth.smb --修改用户密码文件权限为600
//10.10.78.20/smb /sbm cifs credentials=/root/auth.smb 0 0 --编辑/etc/fstab文件,指定路径和用户名密码文件,挂载远程smb共享目录。
四、NFS网络文件系统——主要用于Linux和Linux之间文件共享
第一步:安装NFS网络文件系统nfs-utils,yum install nfs-utils
第二步:清空并保存iptables防火墙配置。iptables -F;service iptables save
第三步:创建nfs文件共享目录,并修改权限为777。
第四步:编辑修改nfs配置文件/etc/exports [root@linuxprobe ~]# vim /etc/exports /nfsfile 192.168.10.*(rw,sync,root_squash) nfs文件共享目录 允许访问的用户IP 读写权限
第五步:使用exports -a命令,同步nfs配置。
第六步:重启rpcbind和nfs-server服务,并将其加入到开机进程中。
第七步:使用showmount -e命令查看远程nfs文件共享配置信息。
第八步:在nfs客户端机器上挂载远程nfs共享目录。 mount -t nfs 192.168.10.10:/nfsfile /nfsfile -t参数指明挂载远程nfs文件系统的格式为nfs 192.168.10.10:/nfsfile /nfsfile nfs defaults 0 0 --编辑/etc/fstab文件







系统运维
2019-10-04 21:32:00
vim 对数字-1 ctrl+x 对数字+1 ctrl+a 将光标下的字母改变大小写 ~ 统计多少个匹配字符 :%s/< match_word >//gn
git 查看提交记录 git log --pretty=oneline <文件名> 查看一次的提记录更改 git show <356f6def9d3fb7f3b9032ff5aa4b9110d4cca87e> 删除远端分支 git push origin --delete 删除远端分支 git branch -r -d 删除本地分支 git branch -d 强制删除本地分支 git branch -D 同步远端删除的分支 git remote prune origin 取消合并分支 git merge --abort 将文件的修改、文件的删除,添加到暂存区。 git add -u 将文件的修改,文件的新建,添加到暂存区。 git add . 将文件的修改,文件的删除,文件的新建,添加到暂存区 git add -A clone一个分支 git checkout -b xxxx origin/yyy 比较上次提交和这次提交中file的变化 git diff HEAD^ HEAD --[file] 比较上次提交和这次提交中统计不同 git diff HEAD^ HEAD --stat 删除最后一次提交 git reset --hard HEAD^ 分支纵向和横向 G H I J \ / \ / D E F \ | / \ \ | / | \|/ | B C \ / \ / A A = = A^0 B = A^ = A^1 = A~1 C = A^2 = A^2 D = A^^ = A^1^1 = A~2 E = B^2 = A^^2 F = B^3 = A^^3 G = A^^^ = A^1^1^1 = A~3 H = D^2 = B^^2 = A^^^2 = A~2^2 I = F^ = B^3^ = A^^3^ J = F^2 = B^3^2 = A^^3^2 G-D-B-A可以认为是主干,其他都是merge进来的其他分支节点。 ^ 横向, ~纵向

awk 打印需要的字段 grep " not_revs " 20180105.err.log |grep type=0 |awk -F "rid=" '{print $2}'|awk -F "," '{print $1}' 其中的$1..$n表示第几例。注:$0表示整个行 简明教程 匹配对应列 awk '$2 ~ /^[0-9][0-9][0-9][0-9]\./ {print $2}' awk200 > awk200-9999
telnet 查看端口是否通 telnet 192.168.1.10 80
nc 连接reids nc 127.0.0.1 6379 执行redis命令 set hello world ==>> +ok
split 按行分割文件 split -l 10 date.file
rename 批量修改文件名字 rename 's/< old_name >/< new_name >/' *
gdb 打印全部的数组内容 set print element 0 打印数组指定长度内容 p *array @len display enable/disable display var_name/disable dispaly 记录gdb的过程 set logging on 保存历史命令 set history save on
aws dynamodb 安装命令行客户端 pip install awscli 配置环境appkey, secretkey aws configure 查看表结构 aws dynamodb describe-table --table-name < table-name > 查询记录 aws dynamodb get-item --table-name < table-name > --key '{ " key1 ": {"S": " abcd "}, " key2 ":{"S":" efgh "}}’ 参考网址1 参考网址2 web客户端镜像拉取 docker pull taydy/dynamodb-manager 运行web客户端镜像 docker run -t -p 8080:80 taydy/dynamodb-manager web客户端网址参考
netstat 常用方式 netstat -antp |grep 8080 常见参数
-a (all)显示所有选项,默认不显示LISTEN相关
-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-n 拒绝显示别名,能显示数字的全部转化成数字。
-l 仅列出有在 Listen (监听) 的服務状态
-p 显示建立相关链接的程序名
-r 显示路由信息,路由表
-e 显示扩展信息,例如uid等
-s 按各个协议进行统计
-c 每隔一个固定时间,执行该netstat命令。
提示:LISTEN和LISTENING的状态只有用-a或者-l才能看到
kafka kafka查看消息消费(新版kafka 2.0) kafka-console-consumer -brokers "172.31.10.189:9092","172.31.10.190:9092","172.31.10.191:9092","172.31.17.238:9092","172.31.17.239:9092" --topic mytopic -offset oldest
kafka查看消息消费(旧版kafka 0.10) kafka-console-consumer --bootstrap-server "172.31.10.189:9092","172.31.10.190:9092","172.31.10.191:9092","172.31.17.238:9092","172.31.17.239:9092" --topic mytopic --from-beginning

nginx 重新载入配置文件 /usr/local/webserver/nginx/sbin/nginx -s reload 重启 Nginx /usr/local/webserver/nginx/sbin/nginx -s reopen 停止 Nginx /usr/local/webserver/nginx/sbin/nginx -s stop
zookeeper 重启zk root@nvm-t-live-kafka-1:/data/zookeeper# ./bin/zkServer.sh start JMX enabled by default Using config: /data/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED root@nvm-t-live-kafka-1:/data/zookeeper# ps -ef|grep zk root 28507 1 18 03:02 pts/0 00:00:01 /bin/bash ./bin/zkServer.sh start root 28511 27490 0 03:02 pts/0 00:00:00 grep zk root@nvm-t-live-kafka-1:/data/zookeeper# ps -ef|grep zoo root 28507 1 20 03:02 pts/0 00:00:26 /usr/lib/jdk1.8.0/bin/java -Dzookeeper.log.dir=. -Dzookeeper.root.logger=INFO,CONSOLE -cp /data/zookeeper/bin/../build/classes:/data/zookeeper/bin/../build/lib/*.jar:/data/zookeeper/bin/../lib/slf4j-log4j12-1.6.1.jar:/data/zookeeper/bin/../lib/slf4j-api-1.6.1.jar:/data/zookeeper/bin/../lib/netty-3.7.0.Final.jar:/data/zookeeper/bin/../lib/log4j-1.2.16.jar:/data/zookeeper/bin/../lib/jline-0.9.94.jar:/data/zookeeper/bin/../zookeeper-3.4.6.jar:/data/zookeeper/bin/../src/java/lib/*.jar:/data/zookeeper/bin/../conf::.:/usr/lib/jdk1.8.0/lib:/usr/lib/jdk1.8.0/jre/lib -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /data/zookeeper/bin/../conf/zoo.cfg

mysql 查看用户权限: show grants 查看正在执行的任务 show processlist 查看mysql引擎innodb状态 show engine innodb status 查看慢查询设置 show variables like '%slow_query%s' 慢查询 查看innodb查询行 show status like '%sInnodb_rows_read%s'

redis 查看内存: info memory 查看客户端 info client 查看key的个数 dbsize 查看所有客户端连接 client list 设置客户端名字 client setName 对最大客户端连接数进行动态设置 config get maxclients, config set maxclients 50 杀掉指定IP地址和端口的客户端 client kill ip:port 阻塞客户端timeout毫秒数 client pause 监控Redis正在执行的命令 monitor 查看阻塞统计 info persistence
系统运维
2019-09-30 14:16:00
最近发现一个业务的数据无法插入redis中,尝试在redis中执行命令时报了如下错误提示:
127.0.0.1:6380> SET Key0 Value0
(error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
大意为:(错误)misconf redis被配置以保存数据库快照,但misconf redis目前不能在硬盘上持久化。用来修改数据集合的命令不能用,请使用日志的错误详细信息。
这是由于强制停止redis快照,不能持久化引起的,运行info命令查看redis快照的状态,如下:
# Persistence loading:0 rdb_changes_since_last_save:973 rdb_bgsave_in_progress:0 rdb_last_save_time:1511138863 rdb_last_bgsave_status:err rdb_last_bgsave_time_sec:9 rdb_current_bgsave_time_sec:-1 aof_enabled:0 aof_rewrite_in_progress:0 aof_rewrite_scheduled:0 aof_last_rewrite_time_sec:-1 aof_current_rewrite_time_sec:-1 aof_last_bgrewrite_status:ok aof_last_write_status:ok
解决方案如下:
运行 config set stop-writes-on-bgsave-error no 命令,关闭配置项stop-writes-on-bgsave-error解决该问题,使数据能正常插入redis。
127.0.0.1:6380> config set stop-writes-on-bgsave-error no OK 127.0.0.1:6380> SET Key0 Value0 OK
但这样做其实是不好的,这仅仅是让程序忽略了这个异常,使得程序能够继续往下运行,实际上数据还是会存储到硬盘失败!
其实这个问题的根源是服务器内存问题。
如果Redis是daemon模式运行,是没法看到详细的日志的,修改配置文件设置logfile参数为文件(默认是stdout,建议以后安装完毕就修改这个参数为文件,不然会丢掉很多重要信息),重启Redis,查看日志,如果内存不够用时程序启动时就有一行警告提示:
“WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.”
(警告:过量使用内存设置为0!在低内存环境下,后台保存可能失败。为了修正这个问题,请在/etc/sysctl.conf 添加一项 'vm.overcommit_memory = 1' ,然后重启(或者运行命令'sysctl vm.overcommit_memory=1' )使其生效。)
echo "vm.overcommit_memory = 1" >>/etc/sysctl.conf sysctl vm.overcommit_memory=1
如果不做修改,程序保存数据时会继续报“MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk”异常,再查看Redis日志,看到有这样的错误提示“Can’t save in background: fork: Cannot allocate memory”,这个提示很明显"Fork进程时内存不够用了!"
通过谷歌查询“Can’t save in background: fork: Cannot allocate memory”这个提示,找到了解决方法:
view plaincopy to clipboardprint?
// 原文:http://pydelion.com/2013/05/27/redis-cant-save-in-background-fork-cannot-allocate-memory/
If you get this error
Can't save in background: fork: Cannot allocate memory
it means that your current database is bigger than memory you have. To fix the issue enable vm.overcommit_memory:
sysctl vm.overcommit_memory=1
To have if after reboot add this line to /etc/sysctl.cnf:
vm.overcommit_memory=1
修改vm.overcommit_memory=1后问题果然解决了。
为什么系统明明还有内存,Redis会说内存不够呢?
网上查了一下,有人也遇到类似的问题,并且给出了很好的分析(详见:http://www.linuxidc.com/Linux/2012-07/66079.htm)。
简单地说:Redis在保存数据到硬盘时为了避免主进程假死,需要Fork一份主进程,然后在Fork进程内完成数据保存到硬盘的操作,如果主进程使用了4GB的内存,Fork子进程的时候需要额外的4GB,此时内存就不够了,Fork失败,进而数据保存硬盘也失败了。
系统运维
2019-11-28 10:10:00
前言
之前将Nexus用于Maven私服以及yum代理,最近在官网上发现最新的Nexus 3.x还支持Docker仓库了,这里测试下。

基础环境
运行nexus需满足下列条件:
1,内存不小于2G,不一定是物理内存,swap也可以。
2,JDK需要1.8以上版本。


具体做法如下:
https://help.sonatype.com/repomanager3/download/download-archives---repository-manager-3
https://download.sonatype.com/nexus/3/nexus-3.14.0-04-unix.tar.gz
1,下载nexus,并解压到某个目录下。我这里放在 /home/nexus 中。这里面有两个目录, nexus-3.14.0-04 放置程序及配置文件, sonatype-work 放置数据文件。

2,修改nexus端口,默认是8081,此步骤可选。
/home/nexus/nexus-3.14.0-04/etc/nexus-default.properties

3,启动nexus,命令如下。这里不推荐使用root用户运行nexus服务。另外,它支持这些参数 start|stop|run|run-redirect|status|restart|force-reload 进行服务管理。
useradd -M nexuser cd /usr/local && chown nexuser:nexuser jdk1.8.0_112 su -c "/home/nexus/nexus-3.14.0-04/bin/nexus start" nexuser 注: 可以创建专有用户,保证对jdk目录有可操作权限


创建Docker私有仓库
1、打开浏览器即可看待nexus页面,默认用户名 admin ,默认密码 admin123
2,创建blob,用于数据存储,默认存放在default中。blob类似bucket,存放源数据及xml等文件。这里创建一个名叫docker的blob,默认存储位置在 /home/nexus/sonatype-work/nexus3/blobs/docker ,此步骤可选。

3、点击设置界面,选择Repositories,点击Create repository,如下图所示:
4、选择仓库类型,这里Docker有三种类型,分别是group、hosted、proxy。这里只演示hosted类型,所以选择docker(hosted),如下图:
5、配置仓库
首先必须为该仓库指定一个唯一的名称,然后是HTTP的端口,最后是docker的api与该仓库进行交互,如下图:

注意这个HTTP端口号也比较关键,在下面修改docker配置参数需用到,后面与该仓库进行交互也需要用到。
最后点击下方Create repository完成创建仓库。

6、安全设置
点击Realms - 将Docker Bearer Token Realm双击Active
https://help.sonatype.com/repomanager3/formats/docker-registry/authentication

修改docker配置
1、编辑配置
第一种
# vim /etc/systemd/system/multi-user.target.wants/docker.service
找到ExecStart属性,在dockerd后面添加--insecure-registry 服务器IP:Docker仓库端口 ,最终如下:
ExecStart=/usr/bin/dockerd --insecure-registry 192.168.1.13:8082
第二种 vim /etc/docker/daemon.json { "insecure-registries":["http://192.168.1.13:8082"]


2、重启服务
# systemctl daemon-reload
# systemctl restart docker

3、查看设置是否生效
# docker info

4、登录nexus私服仓库, 按提示输入账号admin和密码(注意配置文件/root/.docker/config.json)
# docker login 192.168.1.13:8082

5、使用镜像推送测试
docker pull hello-world
docker tag hello-world 192.168.1.13:8082/hello-world
docker push 192.168.1.13:8082/hello-world


注:
打标记
在上传镜像之前需要先打一个tag,用于版本标记。
格式:
# docker tag :/:
例如:
# docker tag hello-world 192.168.1.13:8082/hello-world:latest

上传镜像
# docker push 192.168.1.13:8082/hello-world

拉取镜像
从私服中下载镜像也很简单,执行以下命令即可
# docker pull 192.168.1.13:8082/hello-world

搜索镜像
# docker search 192.168.1.13:8082/hello-world


总结
用起来非常棒!

系统运维
2019-11-26 14:13:00
准备环境 CentOS-7-x86_64 Java8 OpenNMS 23.0.4 minion-23.0.4 sentinel-23.0.4 elasticsearch-6.7.1.tar.gz
OpenNMS 配置
1 配置ActiveMQ
vi $OPENNMS_HOME/etc/opennms-activemq.xml
取消注释
2 添加minion用户
角色选择 ROLE_MINION和 ROLE_ADMIN
minion/minion
启动 Elasticsearch bin/elasticsearch -d
Minion 配置
1 配置控制器 config:edit org.opennms.minion.controller config:property-set location Office-Pittsboro config:property-set http-url http://127.0.0.1:8980/opennms config:property-set broker-url failover:tcp://127.0.0.1:61616 config:update scv:set opennms.http minion minion scv:set opennms.broker minion minion
2 重启 Minion
查看状态 health:check #需要先安装minion-core feature:install minion-core
3 配置侦听 config:edit org.opennms.features.telemetry.listeners-udp-8877 config:property-set name Netflow-5 config:property-set class-name org.opennms.netmgt.telemetry.listeners.udp.UdpListener config:property-set listener.port 8877 config:update
Sentinel 配置
1 配置控制器 config:edit org.opennms.sentinel.controller config:property-set location Sentinel-Flows config:property-set http-url http://127.0.0.1:8980/opennms config:property-set broker-url failover:tcp://127.0.0.1:61616 config:update scv:set opennms.http minion minion scv:set opennms.broker minion minion
查看状态 health:check #需要先安装 sentinel-core feature:install sentinel-core
2 配置数据源 config:edit org.opennms.netmgt.distributed.datasource config:property-set datasource.url jdbc:postgresql://127.0.0.1:5432/opennms config:property-set datasource.username opennms config:property-set datasource.password opennms config:property-set datasource.databaseName opennms config:update
3 配置适配器 config:edit org.opennms.features.telemetry.adapters-netflow5 config:property-set name Netflow-5 config:property-set class-name org.opennms.netmgt.telemetry.adapters.netflow.v5.Netflow5Adapter config:update
4 安装插件 feature:install sentinel-jms feature:install sentinel-flows
查看状态,保证所有服务 [ Success ] health:check
检查数据 curl http://localhost:9200/_cat/shards
Sentinel 自动配置 location = Sentinel-Flows id = 00000000-0000-0000-0000-000000ddba11 http-url = http://127.0.0.1:8980/opennms broker-url = failover:tcp://127.0.0.1:61616 datasource.url = jdbc:postgresql://localhost:5432/opennms datasource.username = opennms datasource.password = opennms datasource.databaseName = opennms name = Netflow-5 class-name = org.opennms.netmgt.telemetry.adapters.netflow.v5.Netflow5Adapter elasticUrl = http://127.0.0.1:9200 sentinel-jms sentinel-flows
系统运维
2019-11-22 10:10:00
错误一
报错信息: Error: rpmdb open failed
报错来源: # yum rpmdb: unable to join the environment error: db3 error(11) from dbenv->open: Resource temporarily unavailable error: cannot open Packages index using db3 - Resource temporarily unavailable (11) error: cannot open Packages database in /var/lib/rpm CRITICAL:yum.main: Error: rpmdb open failed
错误原因: rpm的数据库被损坏。
错误解决方式: 重建rpmdb数据库 # cd /var/lib/rpm # rm -f __db.* # cd .. # rpm --rebuilddb # yum clean all
备注:如果在rpm --rebuilddb时,报错space不足,此时可以清理/var/log目录下的所有文件,在重新执行rebuild操作即可。
系统运维
2019-11-13 17:56:00

导读
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux 除了和 React 一起用外,还支持其它界面库。Redux 体小精悍,仅有 2KB。这里我们需要明确一点:Redux 和 React 之间,没有强绑定的关系。

1. Redux 是什么?
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux 除了和 React 一起用外,还支持其它界面库。Redux 体小精悍,仅有 2KB。这里我们需要明确一点:Redux 和 React 之间,没有强绑定的关系。本文旨在理解和实现一个 Redux,但是不会涉及 react-redux(一次深入理解一个知识点即可,react-redux 将出现在下一篇文章中)。
2. 从零开始实现一个 Redux
我们先忘记 Redux 的概念,从一个例子入手,使用 create-react-app 创建一个项目: toredux。
代码目录: myredux/to-redux 中。
将 public/index.html 中 body 修改为如下:
我们要实现的功能如上图所示,在点击按钮时,能够修改整个应用的字体的颜色。
修改 src/index.js 如下(代码: to-redux/src/index1.js): let state = { color: 'blue' } //渲染应用 function renderApp() { renderHeader(); renderContent(); } //渲染 title 部分 function renderHeader() { const header = document.getElementById('header'); header.style.color = state.color; } //渲染内容部分 function renderContent() { const content = document.getElementById('content'); content.style.color = state.color; } renderApp(); //点击按钮,更改字体颜色 document.getElementById('to-blue').onclick = function () { state.color = 'rgb(0, 51, 254)'; renderApp(); } document.getElementById('to-pink').onclick = function () { state.color = 'rgb(247, 109, 132)'; renderApp(); }
这个应用非常简单,但是它有一个问题:state 是共享状态,但是任何人都可以修改它,一旦我们随意修改了这个状态,就可以导致出错,例如,在 renderHeader 里面,设置 state = {}, 容易造成难以预料的错误。
不过很多时候,我们又的确需要共享状态,因此我们可以考虑设置一些门槛,比如,我们约定,不能直接修改全局状态,必须要通过某个途经才能修改。为此我们定义一个 changeState 函数,全局状态的修改均由它负责。 //在 index.js 中继续追加代码 function changeState(action) { switch(action.type) { case 'CHANGE_COLOR': return { ...state, color: action.color } default: return state; } }
我们约定只能通过 changeState 去修改状态,它接受一个参数 action,包含 type 字段的普通对象,type 字段用于识别你的操作类型(即如何修改状态)。
我们希望点击按钮,可以修改整个应用的字体颜色。 //在 index.js 中继续追加代码 document.getElementById('to-blue').onclick = function() { let state = changeState({ type: 'CHANGE_COLOR', color: 'rgb(0, 51, 254)' }); //状态修改完之后,需要重新渲染页面 renderApp(state); } document.getElementById('to-pink').onclick = function() { let state = changeState({ type: 'CHANGE_COLOR', color: 'rgb(247, 109, 132)' }); renderApp(state); } 抽离 store
尽管现在我们约定了如何修改状态,但是 state 是一个全局变量,我们很容易就可以修改它,因此我们可以考虑将其变成局部变量,将其定义在一个函数内部(createStore),但是在外部还需要使用 state,因此我们需要提供一个方法 getState(),以便我们在 createStore 获取到 state。 function createStore (state) { const getState = () => state; return { getState } }
现在,我们可以通过 store.getState() 方法去获取状态(这里需要说明的是,state 通常是一个对象,因此这个对象在外部其实是可以被直接修改的,但是如果深拷贝 state 返回,那么在外部就一定修改不了,鉴于 redux 源码中就是直接返回了 state,此处我们也不进行深拷贝,毕竟耗费性能)。
仅仅获取状态是远远不够的,我们还需要有修改状态的方法,现在状态是私有变量,我们必须要将修改状态的方法也放到 createStore 中,并将其暴露给外部使用。 function createStore (state) { const getState = () => state; const changeState = () => { //...changeState 中的 code } return { getState, changeState } }
现在,index.js 中代码变成下面这样(to-redux/src/index2.js): function createStore() { let state = { color: 'blue' } const getState = () => state; function changeState(action) { switch (action.type) { case 'CHANGE_COLOR': state = { ...state, color: action.color } return state; default: return state; } } return { getState, changeState } } function renderApp(state) { renderHeader(state); renderContent(state); } function renderHeader(state) { const header = document.getElementById('header'); header.style.color = state.color; } function renderContent(state) { const content = document.getElementById('content'); content.style.color = state.color; } document.getElementById('to-blue').onclick = function () { store.changeState({ type: 'CHANGE_COLOR', color: 'rgb(0, 51, 254)' }); renderApp(store.getState()); } document.getElementById('to-pink').onclick = function () { store.changeState({ type: 'CHANGE_COLOR', color: 'rgb(247, 109, 132)' }); renderApp(store.getState()); } const store = createStore(); renderApp(store.getState());
尽管,我们现在抽离了 createStore 方法,但是显然这个方法一点都不通用,state 和 changeState 方法都定义在了 createStore 中。这种情况下,其它应用无法复用此模式。
changeState 的逻辑理应在外部定义,因为每个应用修改状态的逻辑定然是不同的。我们将这部分逻辑剥离到外部,并将其重命名为 reducer (憋问为什么叫 reducer,问就是为了和 redux 保持一致)。reducer 是干嘛的呢,说白了就是根据 action 的类型,计算出新状态。因为它不是在 createStore 内部定义的,无法直接访问 state,因此我们需要将当前状态作为参数传递给它。如下: function reducer(state, action) { switch(action.type) { case 'CHANGE_COLOR': return { ...state, color: action.color } default: return state; } } createStore 进化版 function createStore(reducer) { let state = { color: 'blue' } const getState = () => state; //将此处的 changeState 更名为 `dispatch` const dispatch = (action) => { //reducer 接收老状态和action,返回一个新状态 state = reducer(state, action); } return { getState, dispatch } }
不同应用的 state 定然是不同的,我们将 state 的值定义在 createStore 内部必然是不合理的。 function createStore(reducer) { let state; const getState = () => state; const dispatch = (action) => { //reducer(state, action) 返回一个新状态 state = reducer(state, action); } return { getState, dispatch } }
大家注意 reducer 的定义,在碰到不能识别的动作时,是直接返回旧状态的,现在,我们利用这一点来返回初始状态。
要想 state 有初始状态,其实很简单,咱们将初始的 state 的初始化值作为 reducer 的参数的默认值,然后在 createStore 中派发一个 reducer 看不懂的动作就可以了。这样 getState 首次调用时,可以获取到状态的默认值。
createStore 进化版2.0 function createStore(reducer) { let state; const getState = () => state; //每当 `dispatch` 一个动作的时候,我们需要调用 `reducer` 以返回一个新状态 const dispatch = (action) => { //reducer(state, action) 返回一个新状态 state = reducer(state, action); } //你要是有个 action 的 type 的值是 `@@redux/__INIT__${Math.random()}`,我敬你是个狠人 dispatch({ type: `@@redux/__INIT__${Math.random()}` }); return { getState, dispatch } }
现在这个 createStore 已经可以到处使用了, 但是你有没有觉得每次 dispatch 后,都手动 renderApp() 显得很蠢,当前应用中是调用两次,如果需要修改1000次 state 呢,难道手动调用 1000次 renderApp() ?
能不能简化一下呢?每次数据变化的时候,自动调用 renderApp()。当然我们不可能将 renderApp() 写在 createStore() 的 dispatch 中,因为其它的应用中,函数名未必叫 renderApp(),而且有可能不止要触发 renderApp()。这里可以引入 发布订阅模式,当状态变化时,通知所有的订阅者。
createStore 进化版3.0 function createStore(reducer) { let state; let listeners = []; const getState = () => state; //subscribe 每次调用,都会返回一个取消订阅的方法 const subscribe = (ln) => { listeners.push(ln); //订阅之后,也要允许取消订阅。 //难道我订了某本杂志之后,就不允许我退订吗?可怕~ const unsubscribe = () => { listenerslisteners = listeners.filter(listener => ln !== listener); } return unsubscribe; }; const dispatch = (action) => { //reducer(state, action) 返回一个新状态 state = reducer(state, action); listeners.forEach(ln => ln()); } //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人 dispatch({ type: `@@redux/__INIT__${Math.random()}` }); return { getState, dispatch, subscribe } }
至此,一个最为简单的 redux 已经创建好了,createStore 是 redux 的核心。我们来使用这个精简版的 redux 重写我们的代码,index.js 文件内容更新如下(to-redux/src/index.js): function createStore() { //code(自行将上面createStore的代码拷贝至此处) } const initialState = { color: 'blue' } function reducer(state = initialState, action) { switch (action.type) { case 'CHANGE_COLOR': return { ...state, color: action.color } default: return state; } } const store = createStore(reducer); function renderApp(state) { renderHeader(state); renderContent(state); } function renderHeader(state) { const header = document.getElementById('header'); header.style.color = state.color; } function renderContent(state) { const content = document.getElementById('content'); content.style.color = state.color; } document.getElementById('to-blue').onclick = function () { store.dispatch({ type: 'CHANGE_COLOR', color: 'rgb(0, 51, 254)' }); } document.getElementById('to-pink').onclick = function () { store.dispatch({ type: 'CHANGE_COLOR', color: 'rgb(247, 109, 132)' }); } renderApp(store.getState()); //每次state发生改变时,都重新渲染 store.subscribe(() => renderApp(store.getState()));
如果现在我们现在希望在点击完 Pink 之后,字体色不允许修改,那么我们还可以取消订阅: const unsub = store.subscribe(() => renderApp(store.getState())); document.getElementById('to-pink').onclick = function () { //code... unsub(); //取消订阅 }
顺便说一句: reducer 是一个纯函数(纯函数的概念如果不了解的话,自行查阅资料),它接收先前的 state 和 action,并返回新的 state。不要问为什么 action 中一定要有 type 字段,这仅仅是一个约定而已(redux 就是这么设计的)
遗留问题:为什么 reducer 一定要返回一个新的 state,而不是直接修改 state 呢。欢迎在评论区留下你的答案。
前面我们一步一步推演了 redux 的核心代码,现在我们来回顾一下 redux 的设计思想:
Redux 设计思想
Redux 将整个应用状态(state)存储到一个地方(通常我们称其为 store)
当我们需要修改状态时,必须派发(dispatch)一个 action( action 是一个带有 type 字段的对象)
专门的状态处理函数 reducer 接收旧的 state 和 action ,并会返回一个新的 state
通过 subscribe 设置订阅,每次派发动作时,通知所有的订阅者。
咱们现在已经有一个基础版本的 redux 了,但是它还不能满足我们的需求。我们平时的业务开发不会像上面所写的示例那样简单,那么就会有一个问题: reducer 函数可能会非常长,因为 action 的类型会非常多。这样肯定是不利于代码的编写和阅读的。
试想一下,你的业务中有一百种 action 需要处理,把这一百种情况编写在一个 reducer 中,不仅写得人恶心,后期维护代码的同事更是想杀人。
因此,我们最好单独编写 reducer,然后对 reducer 进行合并。有请我们的 combineReducers(和 redux 库的命名保持一致) 闪亮登场~ combineReducers
首先我们需要明确一点:combineReducers 只是一个工具函数,正如我们前面所说,它将多个 reducer 合并为一个 reducer。combineReducers 返回的是 reducer,也就是说它是一个高阶函数。
我们还是以一个示例来说明,尽管 redux 不是非得和 react 配合,不过鉴于其与 react 配合最为适合,此处,以 react 代码为例:
这一次除了上面的展示以外,我们新增了一个计数器功能( 使用 React 重构 ===> to-redux2): //现在我们的 state 结构如下: let state = { theme: { color: 'blue' }, counter: { number: 0 } }
显然,修改主题和计数器是可以分割开得,由不同的 reducer 去处理是一个更好的选择。 store/reducers/counter.js 负责处理计数器的state。 import { INCRENENT, DECREMENT } from '../action-types'; export default counter(state = {number: 0}, action) { switch (action.type) { case INCRENENT: return { ...state, number: state.number + action.number } case DECREMENT: return { ...state, number: state.number - action.number } default: return state; } } store/reducers/theme.js
负责处理修改主题色的state。 import { CHANGE_COLOR } from '../action-types'; export default function theme(state = {color: 'blue'}, action) { switch (action.type) { case CHANGE_COLOR: return { ...state, color: action.color } default: return state; } }
每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。 import counter from './counter'; import theme from './theme'; export default function appReducer(state={}, action) { return { theme: theme(state.theme, action), counter: counter(state.counter, action) } }
appReducer 即是合并之后的 reducer,但是当 reducer 较多时,这样写也显得繁琐,因此我们编写一个工具函数来生成这样的 appReducer,我们把这个工具函数命名为 combineReducers。
我们来尝试一下编写这个工具函数 combineReducers:
思路: combineReducers 返回 reducer combineReducers 的入参是多个 reducer 组成的对象 每个 reducer 只处理全局 state 中自己负责的部分 //reducers 是一个对象,属性值是每一个拆分的 reducer export default function combineReducers(reducers) { return function combination(state={}, action) { //reducer 的返回值是新的 state let newState = {}; for(var key in reducers) { newState[key] = reducers[key](state[key], action); } return newState; } }
reducer 将负责返回 state 的默认值。比如本例中,createStore 中 dispatch({type:@@redux/__INIT__${Math.random()}}),而传递给 createStore 的是 combineReducers(reducers) 返回的函数 combination。
根据 state=reducer(state,action),newState.theme=theme(undefined, action), newState.counter=counter(undefined, action),counter 和 theme 两个子 reducer 分别返回 newState.theme 和 newState.counter 的初始值。
利用此 combineReducers 可以重写 store/reducers/index.js import counter from './counter'; import theme from './theme'; import { combineReducers } from '../redux'; //明显简洁了许多~ export default combineReducers({ counter, theme });
我们写的 combineReducers 虽然看起来已经能够满足我们的需求,但是其有一个缺点,即每次都会返回一个新的 state 对象,这会导致在数据没有变化时进行无意义的重新渲染。因此我们可以对数据进行判断,在数据没有变化时,返回原本的 state 即可。
combineReducers 进化版 //代码中省略了一些判断,默认传递的参数均是符合要求的,有兴趣可以查看源码中对参数合法性的判断及处理 export default function combineReducers(reducers) { return function combination(state={}, action) { let nextState = {}; let hasChanged = false; //状态是否改变 for(let key in reducers) { const previousStateForKey = state[key]; const nextStateForKey = reducers[key](previousStateForKey, action); nextState[key] = nextStateForKey; //只有所有的 nextStateForKey 均与 previousStateForKey 相等时,hasChanged 的值才是 false hasChangedhasChanged = hasChanged || nextStateForKey !== previousStateForKey; } //state 没有改变时,返回原对象 return hasChanged ? nextState : state; } } applyMiddleware
官方文档中,关于 applyMiddleware 的解释很清楚,下面的内容也参考了官方文档的内容:
日志记录
考虑一个小小的问题,如果我们希望每次状态改变前能够在控制台中打印出 state,那么我们要怎么做呢?
最简单的即是:
当然,这种方式肯定是不可取的,如果我们代码中派发100次,我们不可能这样写一百次。既然是状态改变时打印 state,也是说是在 dispatch 之前打印 state, 那么我们可以重写 store.dispatch 方法,在派发前打印 state 即可。 let store = createStore(reducer); const next = store.dispatch; //next 的 命令 是为了和中间件的源码一致 store.dispatch = action => { console.log(store.getState()); next(action); }
崩溃信息
假设我们不仅仅需要打印 state,还需要在派发异常出错时,打印出错误信息。 const next = store.dispatch; //next 的命名是为了和中间件的源码一致 store.dispatch = action => { try{ console.log(store.getState()); next(action); } catct(err) { console.error(err); } }
而如果我们还有其他的需求,那么就需要不停的修改 store.dispatch 方法,最后导致这个这部分代码难以维护。
因此我们需要分离 loggerMiddleware 和 exceptionMiddleware. let store = createStore(reducer); const next = store.dispatch; //next 的命名是为了和中间件的源码一致 const loggerMiddleware = action => { console.log(store.getState()); next(action); } const exceptionMiddleware = action => { try{ loggerMiddleware(action); }catch(err) { console.error(err); } } store.dispatch = exceptionMiddleware;
我们知道,很多 middleware 都是第三方提供的,那么 store 肯定是需要作为参数传递给 middleware ,进一步改写: const loggerMiddleware = store => action => { const next = store.dispatch; console.log(store.getState()); next(action); } const exceptionMiddleware = store => action => { try{ loggerMiddleware(store)(action); }catch(err) { console.error(err); } } //使用 store.dispatch = exceptionMiddleware(store)(action);
现在还有一个小小的问题,exceptionMiddleware 中的 loggerMiddleware 是写死的,这肯定是不合理的,我们希望这是一个参数,这样使用起来才灵活,没道理只有 exceptionMiddleware 需要灵活,而不管 loggerMiddleware,进一步改写如下: const loggerMiddleware = store => next => action => { console.log(store.getState()); return next(action); } const exceptionMiddleware = store => next => action => { try{ return next(action); }catch(err) { console.error(err); } } //使用 const next = store.dispatch; const logger = loggerMiddleware(store); store.dispatch = exceptionMiddleware(store)(logger(next));
现在,我们已经有了通用 middleware 的编写格式了。
middleware 接收了一个 next() 的 dispatch 函数,并返回一个 dispatch 函数,返回的函数会被作为下一个 middleware 的 next()
但是有一个小小的问题,当中间件很多的时候,使用中间件的代码会变得很繁琐。为此,redux 提供了一个 applyMiddleware 的工具函数。
上面我们能够看出,其实我们最终要改变的就是 dispatch,因此我们需要重写 store,返回修改了 dispatch 方法之后的 store.
所以,我们可以明确以下几点: applyMiddleware 返回值是 store applyMiddleware 肯定要接受 middleware 作为参数 applyMiddleware 要接受 store 作为入参,不过 redux 源码中入参是 createStore 和 createStore 的入参,想想也是,没有必要在外部创建出 store,毕竟在外部创建出的 store 除了作为参数传递进函数,也没有其它作用,不如把 createStore 和 createStore 需要使用的参数传递进来。 //applyMiddleWare 返回 store. const applyMiddleware = middleware => createStore => (...args) => { let store = createStore(...args); let middle = loggerMiddleware(store); let dispatch = middle(store.dispatch); //新的dispatch方法 //返回一个新的store---重写了dispatch方法 return { ...store, dispatch } }
以上是一个 middleware 的情况,但是我们知道,middleware 可能是一个或者是多个,而且我们主要是要解决多个 middleware 的问题,进一步改写。 //applyMiddleware 返回 store. const applyMiddleware = (...middlewares) => createStore => (...args) => { let store = createStore(...args); //顺便说一句: redux 源码中没有直接把 store 传递过去,而是把 getState 和 dispatch 传递给了 middleware let middles = middlewares.map(middleware => middleware(store)); //现在我们有多个 middleware,需要多次增强 dispatch let dispatch = middles.reduceRight((prev, current) => prev(current), store.dispatch); return { ...store, dispatch } }
不知道大家是不是理解了上面的 middles.reduceRight,下面为大家细致说明一下: /*三个中间件*/ let logger1 = store => dispatch => action => { console.log('111'); dispatch(action); console.log('444'); } let logger2 = store => dispatch => action => { console.log('222'); dispatch(action); console.log('555') } let logger3 = store => dispatch => action => { console.log('333'); dispatch(action); console.log('666'); } let middle1 = logger1(store); let middle2 = logger2(store); let middle3 = logger3(store); //applyMiddleware(logger1,logger3,logger3)(createStore)(reducer) //如果直接替换 store.dispatch = middle1(middle2(middle3(store.dispatch)));
观察上面的 middle1(middle2(middle3(store.dispatch))),如果我们把 middle1,middle2,middle3 看成是数组的每一项,如果对数组的API比较熟悉的话,可以想到 reduce,如果你还不熟悉 reduce,可以查看MDN文档。 //applyMiddleware(logger1,logger3,logger3)(createStore)(reducer) //reduceRight 从右到左执行 middles.reduceRight((prev, current) => current(prev), store.dispatch); //第一次 prev: store.dispatch current: middle3 //第二次 prev: middle3(store.dispatch) current: middle2 //第三次 prev: middle2(middle3(store.dispatch)) current: middle1 //结果 middle1(middle2(middle3(store.dispatch)))
阅读过 redux 的源码的同学,可能知道源码中是提供了一个 compose 函数,而 compose 函数中没有使用 reduceRight,而是使用的 reduce,因而代码稍微有点不同。但是分析过程还是一样的。 compose.js export default function compose(...funcs) { //如果没有中间件 if (funcs.length === 0) { return arg => arg } //中间件长度为1 if (funcs.length === 1) { return funcs[0] } return funcs.reduce((prev, current) => (...args) => prev(current(...args))); }
关于 reduce 的写法,建议像上面的 reduceRight 一样,进行一次分析
使用 compose 工具函数重写 applyMiddleware。 const applyMiddleware = (...middlewares) => createStore => (...args) => { let store = createStore(...args); let middles = middlewares.map(middleware => middleware(store)); let dispatch = compose(...middles)(store.dispatch); return { ...store, dispatch } } bindActionCreators
redux 还为我们提供了 bindActionCreators 工具函数,这个工具函数代码很简单,我们很少直接在代码中使用它,react-redux 中会使用到。此处,简单说明一下: //通常我们会这样编写我们的 actionCreator import { INCRENENT, DECREMENT } from '../action-types'; const counter = { add(number) { return { type: INCRENENT, number } }, minus(number) { return { type: DECREMENT, number } } } export default counter;
在派发的时候,我们需要这样写: import counter from 'xx/xx'; import store from 'xx/xx'; store.dispatch(counter.add()); 当然,我们也可以像下面这样编写我们的 actionCreator: function add(number) { return { type: INCRENENT, number } }
派发时,需要这样编写: store.dispatch(add(number)); 以上代码有一个共同点,就是都是 store.dispatch 派发一个动作。因此我们可以考虑编写一个函数,将 store.dispatch 和 actionCreator 绑定起来。 function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)); } function bindActionCreators(actionCreator, dispatch) { //actionCreators 可以是一个普通函数或者是一个对象 if(typeof actionCreator === 'function') { //如果是函数,返回一个函数,调用时,dispatch 这个函数的返回值 bindActionCreator(actionCreator, dispatch); }else if(typeof actionCreator === 'object') { //如果是一个对象,那么对象的每一项都要都要返回 bindActionCreator const boundActionCreators = {} for(let key in actionCreator) { boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch); } return boundActionCreators; } }
在使用时: let counter = bindActionCreators(counter, store.dispatch); //派发时 counter.add(); counter.minus();
这里看起来并没有精简太多,后面在分析 react-redux 时,会说明为什么需要这个工具函数。
至此,我的 redux 基本已经编写完毕。与 redux 的源码相比,还相差一些内容,例如 createStore 提供的 replaceReducer 方法,以及 createStore 的第二个参数和第三个参数没有提及,稍微看一下代码就能懂,此处不再一一展开。
系统运维
2019-11-13 08:55:00
前提:系统已经安装了图形桌面
开启桌面共享
设置远程访问的密码和基础配置

打开终端进行软件的安装和配置
安装xrdp
$ sudo apt-get install -y xrdp
$ sudo systemctl restart xrdp

安装和配置dconf-editor
$ sudo apt-get install dconf-editor
$ dconf-editor
/org/gnome/desktop/remote-access/require-encryption

然后进行VNC的远程访问
IP:5900

系统运维
2019-11-11 10:55:00
前段时间我们SINE安全收到客户的渗透测试服务委托,在这之前,客户网站受到攻击,数据被篡改,要求我们对网站进行全面的渗透测试,包括漏洞的检测与测试,逻辑漏洞.垂直水平越权漏洞,文件上传漏洞.等等服务项目,在进行安全测试之前,我们对客户的网站大体的了解了一下,整个平台网站,包括APP,安卓端,IOS端都采用的JSP+oracle数据库架构开发,前端使用VUE,服务器是linux centos系统.下面我们将渗透测试过程里,对文件上传漏洞的检测与webshell的分析进行记录,希望更多的人了解什么是渗透测试.
我们直击漏洞根源,查看代码在uplpod.php文件里,可以看到有个lang变量给了language.php,并附加条件,设置的指定文件都存在,才可以将参数值传递过去,代码截图如下:
仔细看,我们看到代码调用了save_file的调用方式,由此可以导致langup值可以伪造,追踪溯源看到该值是对应的WEB前端用户的文件上传功能,在用户文件上传这里,并没有做安全效验与安全白名单拦截机制,导致可以重命名,直接将.jsp的脚本文件上传到网站的根目录下,包括APP也存在该漏洞.
我们SINE安全技术来渗透测试复现一下该文件上传漏洞是如何利用的,首先登录会员,并打开个人资料页面,有个文件上传功能,里面只允许上传图片格式的文件,只允许上传JPG,PNG,GIF,等后缀名的文件,以普通的图片文件来上传,我们抓取POST的上传数据包,将cont1的路径地址改为/beifen/1.jsp,并提交过去,返回数据为成功上传.复制路径,浏览器里打开,发现我们上传的JSP脚本文件执行了,也再一次的证明该漏洞是足以导致网站数据被篡改的,在这之前客户的网站肯定被上传了webshell网站木马文件,随即我们对客户的网站源代码进行全面的人工安全检测与分析,对一句话木马特制eval,加密,包括文件上传的时间点,进行检查,发现在网站的JS目录下存在indax.jsp,浏览器里打开访问,是一个JSP的脚本木马,可以对网站进行篡改,下载代码,新建文件,等网站管理员的操作,同理APP端也是存在同样的漏洞.调用的文件上传功能接口是一样.具体的webshell截图如下:
到这里我们只是渗透测试的一方面,主要是检测的文件上传功能是否存在漏洞,是否可以重命名,自定义上传路径以及文件格式绕过,关于渗透测试中发现的文件上传漏洞如何修复,我们SINE安全给大家一些修复建议与办法,首先对文件的上传格式进行限制,只允许白名单里的jpg,png,gif等格式的文件上传,对自定义的路径地址进行变量覆盖,不允许更改路径地址.对上传的目录做脚本的安全限制,去除JSP的脚本执行权限.
系统运维
2019-11-07 23:25:00


Nginx配置参数优化 Nginx作为高性能web服务器,即使不特意调整配置参数也可以处理大量的并发请求。 以下的配置参数是借鉴网上的一些调优参数,仅作为参考,不见得适于你的线上业务。
分为两个进程: master和worker,其中master为主进程,worker为工作进程,提供服务使用。
worker进程
worker_processes 该参数表示启动几个工作进程,建议和本机CPU核数保持一致,每一核CPU处理一个进程。
worker_rlimit_nofile 它表示Nginx最大可用的文件描述符个数,需要配合系统的最大描述符,建议设置为102400。 还需要在系统里执行ulimit -n 102400才可以。该命令可以把系统的文件描述符个数增加,临时生效 永久生效: 也可以直接修改配置文件/etc/security/limits.conf修改,需要重启后生效 增加: * soft nofile 655350 * hard nofile 655350
默认1024
worker_connections 该参数用来配置每个Nginx worker进程最大处理的连接数,这个参数也决定了该Nginx服务器最多能处理多少客户端请求 (worker_processes * worker_connections),建议把该参数设置为10240,不建议太大。
http和tcp连接

use epoll 使用epoll模式的事件驱动模型,该模型为Linux系统下最优方式。

multi_accept on 使每个worker进程可以同时处理多个客户端请求。

sendfile on 使用内核的FD文件传输功能,可以减少user mode和kernel mode的切换,从而提升服务器性能。

tcp_nopush on 当tcp_nopush设置为on时,会调用tcp_cork方法进行数据传输。 使用该方法会产生这样的效果:当应用程序产生数据时,内核不会立马封装包,而是当数据量积累到一定量时才会封装,然后传输。

tcp_nodelay on 不缓存data-sends(关闭 Nagle 算法),这个能够提高高频发送小数据报文的实时性。 (关于Nagle算法) 【假如需要频繁的发送一些小包数据,比如说1个字节,以IPv4为例的话,则每个包都要附带40字节的头, 也就是说,总计41个字节的数据里,其中只有1个字节是我们需要的数据。 为了解决这个问题,出现了Nagle算法。 它规定:如果包的大小满足MSS,那么可以立即发送,否则数据会被放到缓冲区,等到已经发送的包被确认了之后才能继续发送。 通过这样的规定,可以降低网络里小包的数量,从而提升网络性能。】
keepalive_timeout 定义长连接的超时时间,建议30s,太短或者太长都不一定合适,当然,最好是根据业务自身的情况来动态地调整该参数。
keepalive_requests 定义当客户端和服务端处于长连接的情况下,每个客户端最多可以请求多少次,可以设置很大,比如50000.
reset_timeout_connection on 设置为on的话,当客户端不再向服务端发送请求时,允许服务端关闭该连接。
client_body_timeout 客户端如果在该指定时间内没有加载完body数据,则断开连接,单位是秒,默认60,可以设置为10。
send_timeout 这个超时时间是发送响应的超时时间,即Nginx服务器向客户端发送了数据包,但客户端一直没有去接收这个数据包。 如果某个连接超过send_timeout定义的超时时间,那么Nginx将会关闭这个连接。单位是秒,可以设置为3。
buffer和cache(以下配置都是针对单个请求)
client_body_buffer_size 当客户端以POST方法提交一些数据到服务端时,会先写入到client_body_buffer中,如果buffer写满会写到临时文件里,建议调整为128k。
client_max_body_size 浏览器在发送含有较大HTTP body的请求时,其头部会有一个Content-Length字段,client_max_body_size是用来限制Content-Length所示值的大小的。 这个限制body的配置不用等Nginx接收完所有的HTTP包体,就可以告诉用户请求过大不被接受。会返回413状态码。 例如,用户试图上传一个1GB的文件,Nginx在收完包头后,发现Content-Length超过client_max_body_size定义的值, 就直接发送413(Request Entity Too Large)响应给客户端。 将该数值设置为0,则禁用限制,建议设置为10m。
client_header_buffer_size 设置客户端header的buffer大小,建议4k。
large_client_header_buffers 对于比较大的header(超过client_header_buffer_size)将会使用该部分buffer,两个数值,第一个是个数,第二个是每个buffer的大小。 建议设置为4 8k
open_file_cache 该参数会对以下信息进行缓存: 打开文件描述符的文件大小和修改时间信息; 存在的目录信息; 搜索文件的错误信息(文件不存在无权限读取等信息)。 格式:open_file_cache max=size inactive=time; max设定缓存文件的数量,inactive设定经过多长时间文件没被请求后删除缓存。 建议设置 open_file_cache max=102400 inactive=20s;
open_file_cache_valid 指多长时间检查一次缓存的有效信息。建议设置为30s。
open_file_cache_min_uses open_file_cache指令中的inactive参数时间内文件的最少使用次数, 如,将该参数设置为1,则表示,如果文件在inactive时间内一次都没被使用,它将被移除。 建议设置为2。
压缩 对于纯文本的内容,Nginx是可以使用gzip压缩的。使用压缩技术可以减少对带宽的消耗。 由ngx_http_gzip_module模块支持 nginx.conf配置如下: gzip on; //开启gzip功能 gzip_min_length 1024; //设置请求资源超过该数值才进行压缩,单位字节 gzip_buffers 16 8k; //设置压缩使用的buffer大小,第一个数字为数量,第二个为每个buffer的大小 gzip_comp_level 6; //设置压缩级别,范围1-9,9压缩级别最高,也最耗费CPU资源 gzip_types text/plain application/x-javascript text/css application/xml image/jpeg image/gif image/png; //指定哪些类型的文件需要压缩 gzip_disable "MSIE 6\."; //IE6浏览器不启用压缩 测试: curl -I -H "Accept-Encoding: gzip, deflate" http://www.ami nglinux.com/1.css
curl -x127.0.0.1:80 -I -H "Accept-Encoding: gzip, deflate" http://www.b.com/2.css 此处为了测试gzip_min_length设置为1字节

日志 错误日志级别调高,比如crit级别,尽量少记录无关紧要的日志。 对于访问日志,如果不要求记录日志,可以关闭, 静态资源的访问日志关闭
静态文件过期 对于静态文件,需要设置一个过期时间,这样可以让这些资源缓存到客户端浏览器, 在缓存未失效前,客户端不再向服务期请求相同的资源,从而节省带宽和资源消耗。 虚拟主机中,配置示例如下: location ~* ^.+\.(gif|jpg|png|css|js)$ { expires 1d; //1d表示1天,也可以用24h表示一天。 }
作为代理服务器 Nginx绝大多数情况下都是作为代理或者负载均衡的角色。 因为前面章节已经介绍过以下参数的含义,在这里只提供对应的配置参数: http { proxy_cache_path /data/nginx_cache/ levels=1:2 keys_zone=my_zone:10m inactive=300s max_size=5g; ...; server { proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 2 4k; proxy_busy_buffers_size 4k; proxy_temp_path /tmp/nginx_proxy_tmp 1 2; proxy_max_temp_file_size 20M; proxy_temp_file_write_size 8k; location / { proxy_cache my_zone; ...; } } }
SSL优化 适当减少worker_processes数量,因为ssl功能需要使用CPU的计算。 使用长连接,因为每次建立ssl会话,都会耗费一定的资源(加密、解密) 开启ssl缓存,简化服务端和客户端的“握手”过程。 开启缓存: ssl_session_cache shared:SSL:10m; //缓存为10M ssl_session_timeout 10m; //会话超时时间为10分钟

调整Linux内核参数 作为高性能WEB服务器,只调整Nginx本身的参数是不行的,因为Nginx服务依赖于高性能的操作系统。 以下为常见的几个Linux内核参数优化方法。
编辑配置文件/etc/sysctl.conf
网络相关:

1. net.ipv4.tcp_max_tw_buckets 对于tcp连接,服务端和客户端通信完后状态变为timewait,假如某台服务器非常忙,连接数特别多的话,那么这个timewait数量就会越来越大。 毕竟它也是会占用一定的资源,所以应该有一个最大值,当超过这个值,系统就会删除最早的连接,这样始终保持在一个数量级。 这个数值就是由net.ipv4.tcp_max_tw_buckets这个参数来决定的。 CentOS7系统,你可以使用sysctl -a |grep tw_buckets来查看它的值,默认为32768, 你可以适当把它调低,比如调整到8000,毕竟这个状态的连接太多也是会消耗资源的。 但你不要把它调到几十、几百这样,因为这种状态的tcp连接也是有用的, 如果同样的客户端再次和服务端通信,就不用再次建立新的连接了,用这个旧的通道,省时省力。
2.net.ipv4.tcp_tw_recycle = 1 该参数的作用是快速回收timewait状态的连接。上面虽然提到系统会自动删除掉timewait状态的连接,但如果把这样的连接重新利用起来岂不是更好。 所以该参数设置为1就可以让timewait状态的连接快速回收,它需要和下面的参数配合一起使用。
3. net.ipv4.tcp_tw_reuse = 1 该参数设置为1,将timewait状态的连接重新用于新的TCP连接,要结合上面的参数一起使用。
4. net.ipv4.tcp_syncookies = 1 tcp三次握手中,客户端向服务端发起syn请求,服务端收到后,也会向客户端发起syn请求同时连带ack确认, 假如客户端发送请求后直接断开和服务端的连接,不接收服务端发起的这个请求,服务端会重试多次, 这个重试的过程会持续一段时间(通常高于30s),当这种状态的连接数量非常大时,服务器会消耗很大的资源,从而造成瘫痪, 正常的连接进不来,这种恶意的半连接行为其实叫做syn flood攻击。 设置为1,是开启SYN Cookies,开启后可以避免发生上述的syn flood攻击。 开启该参数后,服务端接收客户端的ack后,再向客户端发送ack+syn之前会要求client在短时间内回应一个序号, 如果客户端不能提供序号或者提供的序号不对则认为该客户端不合法,于是不会发ack+syn给客户端,更涉及不到重试。
5. net.ipv4.tcp_max_syn_backlog 该参数定义系统能接受的最大半连接状态的tcp连接数。客户端向服务端发送了syn包,服务端收到后,会记录一下, 该参数决定最多能记录几个这样的连接。在CentOS7,默认是256,当有syn flood攻击时,这个数值太小则很容易导致服务器瘫痪, 实际上此时服务器并没有消耗太多资源(cpu、内存等),所以可以适当调大它,比如调整到30000。
6. net.ipv4.tcp_syn_retries 该参数适用于客户端,它定义发起syn的最大重试次数,默认为6,建议改为2。
7. net.ipv4.tcp_synack_retries 该参数适用于服务端,它定义发起syn+ack的最大重试次数,默认为5,建议改为2,可以适当预防syn flood攻击。
8. net.ipv4.ip_local_port_range 该参数定义端口范围,系统默认保留端口为1024及以下,以上部分为自定义端口。这个参数适用于客户端, 当客户端和服务端建立连接时,比如说访问服务端的80端口,客户端随机开启了一个端口和服务端发起连接, 这个参数定义随机端口的范围。默认为32768 61000,建议调整为1025 61000。
9. net.ipv4.tcp_fin_timeout tcp连接的状态中,客户端上有一个是FIN-WAIT-2状态,它是状态变迁为timewait前一个状态。 该参数定义不属于任何进程的该连接状态的超时时间,默认值为60,建议调整为6。
10. net.ipv4.tcp_keepalive_time tcp连接状态里,有一个是established状态,只有在这个状态下,客户端和服务端才能通信。正常情况下,当通信完毕, 客户端或服务端会告诉对方要关闭连接,此时状态就会变为timewait,如果客户端没有告诉服务端, 并且服务端也没有告诉客户端关闭的话(例如,客户端那边断网了),此时需要该参数来判定。 比如客户端已经断网了,但服务端上本次连接的状态依然是established,服务端为了确认客户端是否断网, 就需要每隔一段时间去发一个探测包去确认一下看看对方是否在线。这个时间就由该参数决定。它的默认值为7200秒,建议设置为30秒。
11. net.ipv4.tcp_keepalive_intvl 该参数和上面的参数是一起的,服务端在规定时间内发起了探测,查看客户端是否在线,如果客户端并没有确认, 此时服务端还不能认定为对方不在线,而是要尝试多次。该参数定义重新发送探测的时间,即第一次发现对方有问题后,过多久再次发起探测。 默认值为75秒,可以改为3秒。
12. net.ipv4.tcp_keepalive_probes 第10和第11个参数规定了何时发起探测和探测失败后再过多久再发起探测,但并没有定义一共探测几次才算结束。 该参数定义发起探测的包的数量。默认为9,建议设置2。
设置和范例
在Linux下调整内核参数,可以直接编辑配置文件/etc/sysctl.conf,然后执行sysctl -p命令生效 结合以上分析的各内核参数,范例如下 net.ipv4.tcp_fin_timeout = 6 net.ipv4.tcp_keepalive_time = 30 net.ipv4.tcp_max_tw_buckets = 8000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 30000 net.ipv4.tcp_syn_retries = 2 net.ipv4.tcp_synack_retries = 2 net.ipv4.ip_local_port_range = 1025 61000 net.ipv4.tcp_keepalive_intvl = 3 net.ipv4.tcp_keepalive_probes = 2
Nginx运维规范
nginx启动脚本: https://coding.net/u/aminglinux/p/aminglinux-book/git/blob/master/D15Z/etc_init.d_nginx
安装、升级(yum安装or源码安装、编译参数、安装路径等)
服务管理(启动脚本、重启、重载、启动用户)
配置规范: Log格式、路径、命名规则和切割策略 Pid路径 虚拟主机(默认虚拟主机、虚拟主机独立) 静态文件日志和过期缓存时间 防盗链
更改配置(使用自动化工具更改配置文件)
安全规范: 后台地址加用户认证 可写目录禁止解析php 禁止访问.bak文件
系统运维
2019-11-04 21:56:00
#https://blog.csdn.net/u011318077/article/details/87875488 # 由于IO操作非常耗时,程序经常会处于等待状态 # 比如请求多个网页有时候需要等待,gevent可以自动切换协程 # 遇到阻塞自动切换协程,程序启动时执行monkey.patch_all()解决 # 首行添加下面的语句即可from gevent import monkey;monkey.patch_all();
系统运维
2019-10-29 11:23:00
来源: https://blog.csdn.net/Powerful_Fy
rewrite在if中的用法
格式:if (条件判断) { 具体的rewrite规则 } if条件判断语句由Nginx内置变量、逻辑判断符号和目标字符串三部分组成。 其中,内置变量是Nginx固定的非自定义的变量,如,$request_method, $request_uri等。 逻辑判断符号,有=, !=, ~, ~*, !~, !~* !表示相反的意思,~为匹配符号,它右侧为正则表达式,区分大小写,而~*为不区分大小写匹配。 目标字符串可以是正则表达式,通常不用加引号,但表达式中有特殊符号时,比如空格、花括号、分号等,需要用单引号引起来。
示例1:当http请求方法为post时,返回403状态码 if ($request_method = POST) { return 403; }
示例2:通过浏览器标识匹配关键字,禁止IE浏览器访问 if ($http_user_agent ~* MSIE) { return 403; }
限制多个浏览器: if ($http_user_agent ~* "MSIE|firefox|Chrome") { return 403; }
示例3:当请求的文件不存在时,进行重定向或return状态码等处理操作 if(!-f $request_filename) { rewrite 语句; }
示例4:判断uri中某个参数的内容 if($request_uri ~* 'gid=\d{6,8}/') { rewrite 语句; }
#\d表示数字,{6,8}表示数字出现的次数是6到8次,当uri中gid参数的值包含6-8个数字那么执行rewrite语句
rewrite中break和last的用法
两个指令用法相同,但含义不同,需要放到rewrite规则的末尾,用来控制重写后的链接是否继续被nginx配置执行(主要是rewrite、return指令)。
1.break和last在location{}外部时
测试示例: server{ listen 80; server_name test.com; root /data/wwwroot/test.com; rewrite /1.html /2.html; rewrite /2.html /3.html; }
#请求1.html文件时,会被重定向到2.html,然后被重定向到3.html,最后返回的文件为3.html
示例1:在rewrite 指令后面添加break server{ listen 80; server_name test.com; root /data/wwwroot/test.com; rewrite /1.html /2.html break; rewrite /2.html /3.html; }
#请求1.html文件时,会被重定向到2.html,然后直接返回2.html,break在此处的作用就是当匹配第一个rewrite指令成功时,不执行后面的rewrite指令
示例2:当break后面还有location{}的情况 server{ listen 80; server_name test.com; root /data/wwwroot/test.com; rewrite /1.html /2.html break; rewrite /2.html /3.html; location /2.html { return 403; } }
#请求1.html文件时,会返回403状态码,当1.html被重定向到2.html时,break不会匹配后面的rewrite规则,但条件2.html匹配location{}定义的文件2.html,所以会执行return 403
#以上两个示例中,将break换成last效果一样
2.break和last在location{}内部时
测试示例: server{ listen 80; server_name test.com; root /data/wwwroot/test.com; location / { rewrite /1.html /2.html; rewrite /2.html /3.html; } location /2.html { rewrite /2.html /a.html; } location /3.html { rewrite /3.html /b.html; } }
#请求1.html,会经过两次重定向到3.html,3.html又刚好匹配location /3.html{},所以返回b.html,当请求2.html时,会直接返回a.html,因为location /2.html {} 更精准,优先匹配
示例1:在rewrite后面添加break server{ listen 80; server_name test.com; root /data/wwwroot/test.com; location / { rewrite /1.html /2.html break; rewrite /2.html /3.html; } location /2.html { rewrite /2.html /a.html; } location /3.html { rewrite /3.html /b.html; } }
#请求1.html,会返回2.html,不会返回a.html,当break再location {} 内部时,遇到break后,当前location{} 以及后面的location{} 的指令都不再执行
示例2:在rewrite后面添加last server{ listen 80; server_name test.com; root /data/wwwroot/test.com; location / { rewrite /1.html /2.html last; rewrite /2.html /3.html; } location /2.html { rewrite /2.html /a.html; } location /3.html { rewrite /3.html /b.html; } }
#请求1.html时,会返回a.html,在location {} 内部遇到last,当前location {}中剩下的指令不会再执行,但被重定向的url会重新匹配一遍location {}
3.break和last用法总结
1.当rewrite规则在location{}外,break和last作用一样,遇到break或last后,其后续的rewrite/return语句不再执行。但后续有location{}的话,还会近一步执行location{}里面的语句,前提是请求能匹配该location 2.当rewrite规则在location{}里,遇到break后,本location{}与其他location{}的所有rewrite/return规则都不再执行 3.当rewrite规则在location{}里,遇到last后,本location{}里后续rewrite/return规则不执行,但重写后的url再次从头匹配所有location
return的用法
该指令一般用于对请求的客户端直接返回响应状态码。在该作用域内return后面的所有nginx配置都是无效的,可以使用在server、location以及if配置中,除了支持跟状态码,还可以跟字符串或者url链接。
示例1:直接返回状态码 server{ listen 80; server_name www.test.com; return 403; rewrite www.test.net; }
#访问时,直接返回403状态码,return返回内容后,后面的配置rewrite不会执行
示例2:当return在if 判断中时 server { ..... if ($request_uri ~ "\.password|\.bak") { return 404; rewrite /(.*) /index.html; } ..... }
#请求的文件包含.password或.bak时,直接返回404,rewrite不会执行,但if {}外的配置会继续执行,return只在当前作用域中生效
示例3:返回字符串 server{ listen 80; server_name www.test.com; return 200 "hello"; }
#返回字符串必须加上状态码,否则会报错
示例4:返回nginx变量 location /1.html { return 200 "$host $request_uri"; }
示例5:返回url server{ listen 80; server_name www.test.com; return http://www.test.com/index2.html; }
#返回url时,必须以 http://或https://开头
示例6:返回html代码 if ($http_referer ~ 'baidu.com') { return 200 ""; }
#当网站被黑了的时候,从百度点进网站是链接都会跳转到其他网站,可以使用该方法暂时处理 #注意:return http://$host$request_uri ; 在浏览器中会提示"重定向的次数过多"
rewrite的语法规则
格式:rewrite regex replacement [flag]
rewrite 配置可以在server、location以及if配置段内生效
regex 是用于匹配URI的正则表达式,其不会匹配到$host(域名)
replacement 是目标跳转的URI,可以以 http://或者https://开头,也可以省略掉$host,直接写$request_uri部分
flag 用来设置rewrite对URI的处理行为,其中有break、last、rediect、permanent,其中break和last在前面已经介绍过,rediect和permanent的区别在于,前者为临时重定向(302),而后者是永久重定向(301),对于用户通过浏览器访问,这两者的效果是一致的。 但是,对于搜索引擎爬虫来说就有区别了,使用301更有利于SEO。所以,建议replacemnet是以 http://或者https://开头的,flag使用permanent。
示例1:域名跳转 location / { rewrite /(.*) http://www.test.com/$1 permanent; }
# .*为正则表达式,表示uri,用()括起来,在后面的uri中可以调用它,第一次出现的()用$1调用,第二次出现的()用$2调用,以此类推。
示例2:域名跳转的第二种写法 location / { rewrite /.* http://www.test.com$request_uri permanent; }
示例3:文件跳转 server{ listen 80; server_name www.test.com; root /data/wwwroot/test.com; index index.html; if ($request_uri !~ '^/web/') { rewrite /(.*) /web/$1 redirect; } }
#将uri请求的文件重定向到web/目录中去寻找
错误写法1: server{ listen 80; server_name www.test.com; root /data/wwwroot/test.com; index index.html; rewrite /(.*) /web/$1 redirect; }
#这样写会反复循环,直到浏览器最大循环限制次数,哪怕uri包含web/目录了,也会继续重定向/web/web/$1
错误写法2: server{ listen 80; server_name www.test.com; root /data/wwwroot/test.com; index index.html; rewrite /(.*) /web/$1 break; }
#添加break后不会导致循环,但如果uri中包含web/目录的情况下也会被重定向一次,重定向后的uri就是web/web/$1
系统运维
2019-10-28 23:11:00
作者:Hcamael@知道创宇404实验室
时间:2019年10月21日
原文链接: https://paper.seebug.org/1060/

最近在搞路由器的时候,不小心把CFE给刷挂了,然后发现能通过jtag进行救砖,所以就对jtag进行了一波研究。
最开始只是想救砖,并没有想深入研究的想法。
救砖尝试
变砖的路由器型号为:LinkSys wrt54g v8
CPU 型号为:BCM5354
Flash型号为:K8D6316UBM
首先通过jtagulator得到了设备上jtag接口的顺序。
正好公司有一个jlink,但是参试了一波失败,识别不了设备。
随后通过Google搜到发现了一个工具叫: tjtag-pi
可以通树莓派来控制jtag,随后学习了一波树莓派的操作。
树莓派Pins
我使用的是rpi3,其接口编号图如下:
或者在树莓派3中可以使用 gpio readall 查看各个接口的状态:
rpi3中的Python有一个 RPi.GPIO 模块,可以控制这些接口。
举个例子: >>> from RPi import GPIO >>> GPIO.setmode(GPIO.BCM) >>> GPIO.setup(2, GPIO.OUT) >>> GPIO.setup(3, GPIO.IN)
首先是需要进行初始化GPIO的模式,BCM模式对应的针脚排序是上面图中橙色的部门。
然后可以对各个针脚进行单独设置,比如上图中,把2号针脚设置为输出,3号针脚设置为输入。 >>> GPIO.output(2, 1) >>> GPIO.output(2, 0)
使用output函数进行二进制输出 >>> GPIO.input(3) 1
使用input函数获取针脚的输入。
我们可以用线把两个针脚连起来测试上面的代码。
将树莓派对应针脚和路由器的连起来以后,可以运行tjtag-pi程序。但是在运行的过程中却遇到了问题,经常会卡在写flash的时候。通过调整配置,有时是可以写成功的,但是CFE并没有被救回来,备份flash的数据,发现并没有成功写入数据。
因为使用轮子失败,所以我只能自己尝试研究和造轮子了。
jtag
首先是针脚,我见过的设备给jtag一般是提供了5 * 2以上的引脚。其中有一般都是接地引脚,另一半只要知道4个最重要的引脚。
这四个引脚一般情况下的排序是: TDI TDO TMS TCK
TDI表示输入,TDO表示输出,TMS控制位,TCK时钟输入。
jtag大致架构如上图所示,其中TAP-Controller的架构如下图所示:
根据上面这两个架构,对jtag的原理进行讲解。
jtag的核心是TAP-Controller,通过解析TMS数据,来决定输入和输出的关系。所以我们先来看看TAP-Controller的架构。
从上面的图中我们可以发现,在任何状态下,输出5次1,都会回到 TEST LOGIC RESET 状态下。所以在使用jtag前,我们先通过TMS端口,发送5次为1的数据,jtag的状态机将会进入到RESET的复原状态。
当TAP进入到 SHIFT-IR 的状态时, Instruction Register 将会开始接收TDI传入的数据,当输入结束后,进入到 UPDATE-IR 状态时将会解析指令寄存器的值,随后决定输出什么数据。
SHIFT-DR 则是控制数据寄存器,一般是在读写数据的时候需要使用。
讲到这里,就出现一个问题了,TMS就一个端口,jtag如何知道TMS每次输入的值是多少呢?这个时候就需要用到TCK端口了,该端口可以称为时钟指令。当TCK从低频变到高频时,获取一比特TMS/TDI输入,TDO输出1比特。
比如我们让TAP进行一次复位操作: for x in range(5): TCK 0 TMS 1 TCK 1
再比如,我们需要给指令寄存器传入0b10:
1.复位
2.进入RUN-TEST/IDLE状态 TCK 0 TMS 0 TCK 1
3.进入SELECT-DR-SCAN状态 TCK 0 TMS 1 TCK 1
4.进入SELECT-IR-SCAN状态 TCK 0 TMS 1 TCK 1
5.进入CAPTURE-IR状态 TCK 0 TMS 0 TCK 1
6.进入SHIFT-IR状态 TCK 0 TMS 0 TCK 1
7.输入0b10 TCK 0 TMS 0 TDI 0 TCK 1 TCK 0 TMS 1 TDI 1 TCK 0
随后就是进入 EXIT-IR -> UPDATE-IR
根据上面的理论我们就可以通过写一个设置IR的函数: def clock(tms, tdi): tms = 1 if tms else 0 tdi = 1 if tdi else 0 GPIO.output(TCK, 0) GPIO.output(TMS, tms) GPIO.output(TDI, tdi) GPIO.output(TCK, 1) return GPIO.input(TDO) def reset(): clock(1, 0) clock(1, 0) clock(1, 0) clock(1, 0) clock(1, 0) clock(0, 0) def set_instr(instr): clock(1, 0) clock(1, 0) clock(0, 0) clock(0, 0) for i in range(INSTR_LENGTH): clock(i==(INSTR_LENGTH - 1), (instr>>i)&1) clock(1, 0) clock(0, 0)
把上面的代码理解清楚后,基本就理解了TAP的逻辑。接下来就是指令的问题了,指令寄存器的长度是多少?指令寄存器的值为多少时是有意义的?
不同的CPU对于上面的答案都不一样,通过我在网上搜索的结果,每个CPU应该都有一个bsd(boundary scan description)文件。本篇文章研究的CPU型号是 BCM5354 ,但是我并没有在网上找到该型号CPU的bsd文件。我只能找了一个相同厂商不同型号的CPU的bsd文件进行参考。
bcm53101m.bsd
在该文件中我们能看到jtag端口在cpu端口的位置: "tck : B46 , " & "tdi : A57 , " & "tdo : B47 , " & "tms : A58 , " & "trst_b : A59 , " & attribute TAP_SCAN_RESET of trst_b : signal is true; attribute TAP_SCAN_IN of tdi : signal is true; attribute TAP_SCAN_MODE of tms : signal is true; attribute TAP_SCAN_OUT of tdo : signal is true; attribute TAP_SCAN_CLOCK of tck : signal is (2.5000000000000000000e+07, BOTH);
能找到指令长度的定义: attribute INSTRUCTION_LENGTH of top: entity is 32;
能找到指令寄存器的有效值: attribute INSTRUCTION_OPCODE of top: entity is "IDCODE (11111111111111111111111111111110)," & "BYPASS (00000000000000000000000000000000, 11111111111111111111111111111111)," & "EXTEST (11111111111111111111111111101000)," & "SAMPLE (11111111111111111111111111111000)," & "PRELOAD (11111111111111111111111111111000)," & "HIGHZ (11111111111111111111111111001111)," & "CLAMP (11111111111111111111111111101111) " ;
当指令寄存器的值为 IDCODE 的时候,IDCODE寄存器的输出通道开启,我们来看看IDCODE寄存器: attribute IDCODE_REGISTER of top: entity is "0000" & -- version "0000000011011111" & -- part number "00101111111" & -- manufacturer's identity "1"; -- required by 1149.1
从这里我们能看出IDCODE寄存器的固定输出为: 0b00000000000011011111001011111111
那我们怎么获取TDO的输出呢?这个时候数据寄存器DR就发挥作用了。 TAP状态机切换到SHIFT-IR 输出IDCODE到IR中 切换到SHIFT-DR 获取INSTRUCTION_LENGTH长度的TDO输出值 退出
用代码形式的表示如下: def ReadWriteData(data): out_data = 0 clock(1, 0) clock(0, 0) clock(0, 0) for i in range(32): out_bit = clock((i == 31), ((data >> i) & 1)) out_data = out_data | (out_bit << i) clock(1,0) clock(0,0) return out_data def ReadData(): return ReadWriteData(0) def WriteData(data): ReadWriteData(data) def idcode(): set_instr(INSTR_IDCODE) print(hex(self.ReadData()))
因为我也是个初学者,边界扫描描述文件中的内容并不是都能看得懂,比如在边界扫描文件中并不能看出BYPASS指令是做什么的。但是在其他文档中,得知BYPASS寄存器一般是用来做测试的,在该寄存器中,输入和输出是直连,可以通过比较输入和输出的值,来判断端口是否连接正确。
另外还有边界扫描寄存器一大堆数据,也没完全研究透,相关的资料少的可怜。而且也找不到对应CPU的文档。
当研究到这里的时候,我只了解了jtag的基本原理,只会使用两个基本的指令(IDCODE, BYPASS)。但是对我修砖没任何帮助。
没办法,我又回头来看tjtag的源码,在tjtag中定义了几个指令寄存器的OPCODE: INSTR_ADDRESS = 0x08 INSTR_DATA = 0x09 INSTR_CONTROL = 0x0A
照抄着tjtag中flash AMD的操作,可以成功对flash进行擦除,写入操作读取操作。但是却不知其原理。
这里分享下我的脚本: jtag.py
flash文档: https://www.dataman.com/media/datasheet/Samsung/K8D6x16UTM_K8D6x16UBM_rev16.pdf
接下来将会对该flash文档进行研究,并在之后的文章中分享我后续的研究成果。
系统运维
2019-10-25 18:34:00
从前一篇influxdb的文章 prometheus基于influxdb的监控数据持久化存储方案 完成之后,就一直在折腾influxdb发布测试和生产环境的问题,经过接近2个月的验证,最终发现使用influxdb自带cq的方案对存储的io要求挺高的。
为什么会说cq对io要求高?是因为进行方案验证时,均是在集成测试和测试的openstack controller节点上进行的测试,刚好这几台主机是配的ssd,非常愉快就完成了POC。但是上线的时候,测试环境申请的openstack的虚拟机,结果发现influxdb一会儿就会重启,重启原因就是influxdb报出oom的异常,同时也有“engine: error syncing wal”的异常 # Aug 14 18:37:08 sz680961 influxd: fatal error: runtime: cannot allocate memory Aug 14 18:37:05 sz680961 influxd: ts=2019-08-14T10:37:05.227282Z lvl=info msg="Write failed" log_id=0HFvsoi0000 service=write shard=14 error="engine: error syncing wal"
当时没有想到是io问题导致的,又在controller上建了相同内存32G的influxdb docker进行同时测试,发现以前的测试数据并没有问题,分析差异主要是磁盘io,原因可能是openstack虚拟机使用ceph磁盘,io和本地ssd相差了几个量级,导致cq执行时有大量数据缓存在数据库中,同时又有大量数据插入io响应不过来,最终用完可用的操作系统内存导致进行崩掉。
临近上线,没有更好的方案只能硬着头皮上线,主要原因是1. 生产环境数据比测试环境数据量少,2.如果cq执行还是要导致influxdb崩溃,就先把cq关闭。
就这样留着缺陷上线,观察了一个月偶尔还是存在influxdb oom重启的情况,估计和vmware集群整体的io压力有关。
生产环境的数据量会逐渐增大,这样下去肯定不是办法,但如果不进行降频又满足了长时间存储监控数据的需求,经过新一轮设计和poc制定了变化相对较小的方案: 关闭influxdb cq 自行研发跑批python,主要变化就是将cq中一次性加载和计算所有measurement中数据修改为自行控制,依次执行measurement的降频sql
经过poc验证在使用ssd情况下,相同数据量由原cq需要4分钟降低为只需要是30s,而且内存占用跑批前后基本无变化。
如下是开发的跑批日志页面,如果出现跑批失败,还可以进行重跑。
系统运维
2019-10-22 20:44:00
对于编程中琳琅满目的算法,本人向来是不善此道也不精于此的,而说起排序算法,也只是会冒泡排序。还记得当初刚做开发工作面试第一家公司时,面试官便让手写冒泡排序(入职之后才知道,这面试官就是一个冒泡排序"病态"爱好者,逢面试必考冒泡排序-__-)。后来看吴军的一些文章,提到提高效率的关键就是少做事情不做无用功,便对这不起眼的排序算法有了兴趣。刚好今天周末有闲,遂研究一二,与各位道友共享。
冒泡排序时间之所以效率低,就是因为将所有数都一视同仁不做区分挨个比较,这是最普通的做事方法,所以效率也是最普通的,时间复杂度为N的平方;而归并排序效率高,则是采用了分治的思想,将一个整体分成多个小份,每个小份排好序之后再互相比较,这样就比冒泡快了不少,时间复杂度为NlogN;快速排序的平均时间复杂度也是NlogN,但是实际的耗费时间会比归并排序快两三倍(当然快排在最坏的情况下时间复杂度还是N的平方,比归并排序大),它的平均执行时间能比归并更快一些是因为它每次分组时不是随机分组而是相对有序的分组,即先从数组中随机取一个数作为基数,然后将数据移动,使得基数一边的数都比它小,另一边的数都比它大,再在两边各取一个基数进行相同的移动、分组操作,递归下去,这样每个细分的小组都在整体的大数组中有个位置,合并时直接按从小到大将各个分组合并起来即可,所以一般情况下会比归并快一些。
了解了思想之后,再用代码实现相对就会容易很多。此处就再借用一个直观一点的例子来说明归并与快排二者的区别。假设有1000个学生,想对他们的成绩进行排序。方法1借用归并排序的思想,具体这样做:将这1000个人分成10组,将每组的100人进行排序,排完之后再在各组之间从小到大依次进行比较,最后得到整个的成绩排名。方法2借用快速排序的思想,具体需这样做:将1000个人也是分成10组,但是是按分数段分,0-10分的放在一组,10-20分的放在一组,20-30分的放在一组,依次类推,分完组之后再在各个小组中进行排序,而当你合并各个小组时,只需将其按从小到大的顺序直接合并就行,无需跟方法1一样将各小组中的数据取出来跟其他小组中的数据挨个比较。看到这里,想必各位道友对快排比归并排序还要快一些的原因就有了解了。
算法可以理解成做事的技巧或者说套路,我们对其的理解可以不止于编程,完全可以推广出去。比如归并的分治法,将一个大事情拆解成多个小事件,解决起来就会方便很多。闲话扯了一大堆,下面就将我自己写的排序给大家贴出来,附带上注释讲解,如果有之前不了解的道友,相信看完之后便会念头通透,原地飞升 >_< 。 正文 [erji]归并排序[/erji] // 归并排序 public static void mergeSort (int[] arr) { // 建一个临时数据来存放数据 int[] temp = new int[arr.length]; mergeSort(arr, 0, arr.length - 1, temp); } private static void mergeSort(int[] arr, int left, int right, int[] temp) { if (left < right) { // 如果起始下标跟结束下标差值小于1,则不进行操作 int mid = (left + right) / 2; mergeSort(arr, left, mid, temp); // 分组,将左边分为一组,递归调用进行排序 mergeSort(arr, mid+1, right, temp); // 将右边分为一组 merge(arr, left, mid, right, temp); //将左右分组合并 } } private static void merge(int[] arr, int left, int mid, int right, int[] temp) { int i = left; // 定义左指针 int j = mid + 1; // 定义右指针 int t = 0; // 给temp临时数组用的指针 while (i <= mid && j <= right) { // 设置左右指针的移动边界 if (arr[i] <= arr[j]) { // 此处是升序,故谁小谁先赋给临时数组 temp[t++] = arr[i++]; } else { temp[t++] = arr[j++]; } } while (i <= mid) { // 如果左边有剩余,则放在temp中 temp[t++] = arr[i++]; } while (j <= right) { // 如果右边有剩余,依次放入temp中 temp[t++] = arr[j++]; } t = 0; // 此时temp中已经是arr数组中下标从left到right之间排好序的数据了,因为temp每次都是从0开始赋值,所以需将排好序的数放回arr的对应位置 while (left <= right) { // 将left到right之间排好序的数据放回arr中,此时left到right之间的数就是最终排好序的数 arr[left++] = temp[t++]; } }
快速排序 // 快速排序 public static void quickSort (int[] arr, int left, int right) { // 先将异常情况处理掉 if (arr == null || arr.length < 2) { return; } if (right <= left) { return; } if (right - left == 1 && arr[left] <= arr[right]) { return; } // 取第一个数为基准数(基数取哪个都行,此处是为了方便) int index = arr[left]; int i = left + 1; // 左指针 int j = right; // 右指针 while (i < j && i < right && j > left) { // 设置指针的移动边界 while (arr[j] > index && j > left) {j--;} // 找到从右边数第一个比index小的数 while (arr[i] < index && i < right) {i++;} // 找到从左边数第一个比index大的数 if (i < j) { // 交换这两个数 如果i == j,说明二者定位到了同一个位置,则不用交换;如果i > j,说明二者已经相遇然后背向而行了,也不交换 int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 执行完上面循环后,arr已经是左边比index小,右边比index大的数组了,只是基准数仍在基准位置left处,需放到它应该在的位置 if (j != left && arr[j] != arr[left]) { // j最后停留位置的数,肯定是一个小于等于index的值,所以如果不是同一个位置的话,直接将二者调换一下位置即可 int temp = arr[j]; arr[j] = arr[left]; arr[left] = temp; } quickSort(arr, left, j-1); // 将基准数左边排序 quickSort(arr, j+1, right); // 将基准数右边排序 } 本文地址: https://www.linuxprobe.com/amount-pre-line.html
系统运维
2019-10-21 22:37:00
摘要:本文将就Observability中的日志聚合、分布式跟踪及具体应用中结合使用进行展开说明。
一、Observability
Observability是一个最近几年开始在监控社区流行的术语。本文将Observability视为一种理念,一种监控的超集,包括监控、日志聚合、分布式跟踪,可以实时更深入地观察系统。本文将就其中的日志聚合、分布式跟踪及具体应用中结合使用进行展开说明。
二、日志与调用链的探索式查询
微服务、云和容器化架构的出现,改变了我们构建系统的方式。应用程序是分布式的,而且瞬息万变。加之底层的基础设施和网络服务愈加健壮,日常系统运维的大部分工作将来自应用程序层或者是不同应用程序之间的复杂交互调用。
对于复杂的跨系统调用,一次请求可能需要后台几台或上百台节点的支持。此时具体到一次请求已经很难通过人力确认其处理的完整流程,此时最能反映每次请求处理过程的应该是分布式追踪(下文简称调用链)。
调用链是从一次具体请求的全局角度看待问题,当细粒具体到一个节点时,应用系统自身打印的日志最能说明当前节点处理逻辑。
下面通过一张简图来说明调用链和日志聚合做的事情:
调用链的作用是将一次请求所经过的所有节点和关键操作进行记录并汇总展示出来,就像图中绿色箭头,能够提供一个全局的视角去看待一次请求。
日志聚合的作用是将所有节点和系统产生的日志进行汇总整理,并提供给用户一个有效并友好的查询能力。
但是我们在具体使用过程中往往是这样的:
从调用链进来以后发现了一个问题,然后切换到日志聚合去根据特定属性查询对应的日志信息,通过排查日志信息发现还需要再次去查询与之关联的调用链信息……如此往返多次。
日志与调用链的探索式查询对于这种经典场景提供了一种新的闭环处理问题模式:
从调用链入口进入,可以根据调用链关联到具体应用的与当前调用链相关的日志,根据日志也可以关联到具体一条调用链;从日志入口进入,可以根据日志关联到与当前日志相关的具体一条调用链,根据一条调用链又可以关联到与当前调用链相关联的日志。而且两种模式可以相互切换。
三、举个栗子
用户小明通过日志聚合搜索发现有A系统一段日志有异常信息,此时他可以通过此条日志关联找出对应的调用过程a。通过观察a这条调用链小明发现,是由于a上的节点a[2]超时导致。此时小明可以从调用链关联到与节点a[2]相关的日志内容从而确定问题所在(具体效果见下文)。
四、整体架构设计
4.1 数据抓取:
应用集群中的机器上部署的agent用于数据收集和上送,探针内嵌在容器(tomcat等)用于为应用画像和收集应用信息
4.2 数据传输:
agent将处理过后的日志通过mq上送到监控服务器
4.3 数据处理及存储:
监控服务器将采集上来的数据进行处理并将其存入ES,方便用户通过特定特征快速定位
4.4 数据展示:
将数据进行可视化展示,并提供方便的可视化自定义查询服务
五、具体实现
在介绍调用链和日志聚合具体实现之前需要明确的几个概念:
5.1 中间件劫持技术
通过在中间件启动时动态将我们自己的代码行为植入到中间件的各种行为中的技术。比如在tomcat启动时动态在tomcat处理请求的开始位置添加代码劫持,则能够实现在tomcat执行处理请求逻辑之前进行服务调用画像等功能。更多能力和实现方式可以参考: http://chuansong.me/n/603660351655
5.2 traceId
通过中间件劫持技术在服务调用最前端产生且能够唯一确定一条调用链的id。
主要实现逻辑: 在应用容器启动时,使用中间件劫持技术在服务调用入口和应用日志写文件入口位置添加劫持点 在发生服务调用时生成调用链元数据和上下文 当应用写日志时通过写文件入口劫持点获取当前调用的调用链上下文,将traceId与应用日志一同写入应用日志文件 日志归集将生成的日志文件聚合整理上送到监控服务器 监控服务器将收集到的日志信息,进行处理并存入es web页面将存储在es中的数据进行展示
核心逻辑如下图:
六、调用链和日志聚合实现
调用链部分分为:模型设计、服务端信息收集(轻/重)、方法级信息收集(轻/重)、客户端信息收集(轻/重)、调用链协议设计(轻/重)、调用链上下文传递、调用信息记录及传递、调用数据统计处理几个关键过程。关键技术为中间件劫持增强框架、调用模型设计和调用链上下文传递。
应用日志部分分为:日志归集,日志内容处理传输,服务端日志处理及存储等几个关键步骤。关键技术为:服务画像技术、日志归集。
七、效果展示
7.1 调用链入口
通过特定条件搜素出关心的具体一次调用过程,点击进入调用的详细过程界面。
点击右侧的关联按钮可快速定位到与之相关联日志。
7.2 日志入口
通过特定特征(图中为按照Hello关键字进行搜索)搜索出符合条件的日志。
通过点击具体日志即可进入对应的调用过程。
参考资料
更多详情: https://uavorg.github.io/documents/uavdoc_architecture/moniorframework/diao-yong-lian-shu-ju-sheng-cheng.html
下载UAVStack的源码( https://github.com/uavorg)
下载AllInOne开发演示版( https://uavorg.github.io/main/)
作者:李崇
来源:宜信技术学院
系统运维
2019-10-21 13:09:00
注:kuberntes版本为1.15
什么是 Ingress
Ingress 是一个提供对外服务的路由和负载均衡器,其本质是个nginx控制器服务。
k8s文档上Ingress经典数据链路图: internet | [ Ingress ] --|-----|-- [ Services ]
对博客进行改造
构建Dockefile
先容器化整个Hexo项目,构建 Dockefile ,这里采用nginx + 静态资源的形式部署(主要为了节约内存CPU): FROM nginx:1.13.0-alpine LABEL maintainer="hexo-shikanon-blog " # 装载编译后的文件对外访问 COPY ./public /usr/share/nginx/html
构建Deployment
构建一个 Deployment 服务将其部署上kubernetes: apiVersion: apps/v1 kind: Deployment metadata: name: nginx-hexo-blog-delopyment labels: webtype: staticblog spec: replicas: 2 selector: matchLabels: webtype: staticblog template: metadata: labels: webtype: staticblog function: blog spec: containers: - name: hexo-blog image: nginx-hexo-blog:0.0.1 ports: - containerPort: 80
构建Service暴露服务端口
构建一个 Service 暴露统一的服务端口: apiVersion: v1 kind: Service metadata: name: static-blog spec: selector: webtype: staticblog ports: - protocol: TCP port: 80 targetPort: 80 # deployment的端口,
这里创建一个名称为 "static-blog" 的 Service 对象,它会将请求代理到使用 TCP 端口 targetPort,并且具有标签 "webtype: staticblog" 的 Pod 上。
查看端口信息: $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.13.0.1 443/TCP 10d static-blog ClusterIP 10.13.83.44 80/TCP 8h
测试端口是否可以访问: $ curl -I 10.13.83.44 HTTP/1.1 200 OK Server: nginx/1.13.0 Date: Wed, 16 Oct 2019 16:51:13 GMT Content-Type: text/html Content-Length: 71636 Last-Modified: Mon, 29 Jul 2019 19:25:29 GMT Connection: keep-alive ETag: "5d3f4829-117d4" Accept-Ranges: bytes
构建Ingress服务
最后一步,构建 Ingress 服务对外部提供服务和反向代理: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: reverse-proxy annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: www.shikanon.com http: paths: - backend: serviceName: static-blog servicePort: 80
完成!
构建HTTPS网站
用secret类型对象保存密钥数据
Secret 对象类型用来保存敏感信息,例如密码、OAuth 令牌和 ssh key,其中 ssh key 就是一个经典的应用。
Secret 参数用例: kubectl create secret -h Create a secret using specified subcommand. Available Commands: docker-registry Create a secret for use with a Docker registry generic Create a secret from a local file, directory or literal value tls Create a TLS secret Usage: kubectl create secret [flags] [options]
创建 Secret 加密对象: kubectl create secret tls shikanon-ssh-key-secret --cert=/home/shikanon/web/www/ssl/cert.pem --key=/home/shikanon/web/www/ssl/private.key
修改Ingress: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: reverse-proxy annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: www.shikanon.com http: paths: - backend: serviceName: static-blog servicePort: 80 tls: - hosts: - www.shikanon.com secretName: shikanon-ssh-key-secret
注:一个Ingress只能支持一个tls
系统运维
2019-10-20 21:52:05
使用国内镜像 mv /etc/apt/sources.list /etc/apt/sources.list.bak echo "deb http://mirrors.163.com/debian/ jessie main non-free contrib" >> /etc/apt/sources.list echo "deb http://mirrors.163.com/debian/ jessie-proposed-updates main non-free contrib" >>/etc/apt/sources.list echo "deb-src http://mirrors.163.com/debian/ jessie main non-free contrib" >>/etc/apt/sources.list echo "deb-src http://mirrors.163.com/debian/ jessie-proposed-updates main non-free contrib" >>/etc/apt/sources.list 更新 apt-get 指令 apt-get update 安装 yum 命令 apt-get install vim 添加 ls 命令 vim ~/.bashrc 在最后一行添加 alias ll='ls $LS_OPTIONS -l' 使之生效 source ~/.bashrc
系统运维
2019-10-18 16:12:00
node_modules/x-pack/plugins/translations/translations/zh-CN.json

kbn.discoverTitle
kbn.home.welcomeTitle
xpack.security.loginPage.welcomeTitle
系统运维
2019-10-17 12:01:00
首先 :谷歌浏览器右键打开属性,在箭头所指的位置复制粘贴 -no-sandbox。(需要空一格再写入 -no-sandbox)

其次 :你打开谷歌浏览器可以看到如下提醒,提醒你,稳定性和安全性会有所下降,但表担心,只要能打开浏览器就很惊喜了!
剩下的问题就很好解决了 !!点击右上角的 三个点 ---》点击 设置 ---》拖到最下边 高级设置 ---》再拖到最下边 重置 ---》完美
最后,打开浏览器就可以用了。
ps:2019/10/15补充
谷歌浏览器不知道怎么了,最近打开都是崩溃,如果你们的网随便下载,最好重新下一个吧!我现在几乎不重置了,直接在--no-sandbox下使用!!
系统运维
2019-10-15 12:48:00
1、什么是ansible?
ansible是一个部署一群远程主机的自动化运维工具,基于Python开发,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,它不需要安装客户端,通过SSH协议实现远程节点和管理节点之间的通信,实现了批量系统配置、批量程序部署、批量运行命令等功能。
ansible是基于模块工作的,模块可以由任何语言开发,它不仅支持命令行使用模块,也支持编写yaml格式的playbook。
2、ansible的安装
两台主机,jin-10,jin-11,我们只需要在jin-10上安装ansible即可。 [root@jin-10 ~]# yum install -y ansible
然后把jin-10上的公钥文件id_rsa.pub复制到jin-11上的/root/.ssh/authorized_keys里,同时,对本机也进行密钥认证(把/root/.ssh/id_rsa.pub文件的内容复制到authorized_keys里)。 [root@jin-10 ~]# ssh jin-10 Last login: Tue Sep 3 12:27:50 2019 from jin-11 [root@jin-10 ~]# ssh jin-11 Last login: Tue Sep 3 12:24:22 2019 from jin-10
可以看到,jin-10用ssh的方式连接本机和jin-11都不用密码和密钥认证。
然后我们设置一个主机组,用来告诉Ansible需要管理哪些主机。编辑文件vim /etc/ansible/hosts,内容如下: [testhost] 127.0.0.1 jin-11
其中,testhost为自定义的主机组名字,然后是两个主机的IP(或主机名)。
3、ansible的使用
ansible模块Module
bash无论在命令行上执行,还是bash脚本中,都需要调用cd、ls、copy、yum等命令;module就是ansible的“命令”,module是ansible命令行和脚本中都需要调用的。常用的ansible module有yum、copy、template等。
在bash,调用命令时可以跟不同的参数,每个命令的参数都是该命令自定义的;同样,ansible中调用module也可以跟不同的参数,每个module的参数也都是由module自定义的。
在命令行中,-m后面接调用module的名字,-a后面接调用module的参数: [root@jin-10 ~]# ansible all -m copy -a "src=/etc/hosts dest=/tmp/hosts" jin-11 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "checksum": "843eede91ec66c16e38ddb67dee1bb173ea7fe9c", "dest": "/tmp/hosts", "gid": 0, "group": "root", "mode": "0644", "owner": "root", "path": "/tmp/hosts", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 182, "state": "file", "uid": 0 } 127.0.0.1 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "checksum": "843eede91ec66c16e38ddb67dee1bb173ea7fe9c", "dest": "/tmp/hosts", "gid": 0, "group": "root", "mode": "0644", "owner": "root", "path": "/tmp/hosts", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 182, "state": "file", "uid": 0 }
在playbook脚本中,tasks中的每一个action都是对module的一次调用。在每个action中:冒号前面是module的名字,冒号后面是调用module的参数: --- tasks: - name: ensure apache is at the latest version yum: pkg=httpd state=latest - name: write the apache config file template: src=templates/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf - name: ensure apache is running service: name=httpd state=started
下面是一些常用的module:
调试和测试类的module: ping - ping一下你的远程主机,如果可以通过ansible成功连接,那么返回pong debug - 用于调试的module,只是简单打印一些消息,有点像linux的echo命令。
文件类的module: copy - 从本地拷贝文件到远程节点 template - 从本地拷贝文件到远程节点,并进行变量的替换 file - 设置文件的属性
linux上常用的操作: user - 管理用户账户 yum - redhat系linux上的安装包管理 service - 管理服务 firewalld - 管理防火墙中的服务和端口
执行Shell命令: shell - 在节点上执行shell命令,支持$HOME和"<", ">", "|", ";" and "&" command - 在远程节点上面执行命令,不支持$HOME和"<", ">", "|", ";" and "&"
3.1 ansible远程执行命令
例:ansible testhost -m command -a 'w'
此处的testhost为主机组名(也可以直接写一个ip,针对某一台机器来执行命令),-m后边是模块名字,-a后面是命令 [root@jin-10 ~]# ansible testhost -m command -a 'w' jin-11 | CHANGED | rc=0 >> 09:07:34 up 21:59, 2 users, load average: 0.00, 0.01, 0.05 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root pts/0 192.168.154.1 二11 20:39m 0.33s 0.33s -bash root pts/1 jin-10 09:07 1.00s 0.28s 0.02s w 127.0.0.1 | CHANGED | rc=0 >> 09:07:34 up 23:03, 3 users, load average: 0.27, 0.14, 0.19 USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT root pts/0 192.168.154.1 二10 6.00s 6.63s 5.48s ssh jin-10 root pts/1 fe80::d601:fb6f: 二12 6.00s 7.09s 0.00s ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/21f0e6a9ae -tt 127.0.0.1 /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1567559253.0-170559931011383/AnsiballZ_command.py && sleep 0' root pts/4 localhost 09:07 1.00s 0.51s 0.01s w
针对某一台机器来执行命令: [root@jin-10 ~]# ansible jin-11 -m command -a 'hostname' jin-11 | CHANGED | rc=0 >> jin-11
模块shell也可以同样实现: [root@jin-10 ~]# ansible testhost -m shell -a 'hostname' jin-11 | CHANGED | rc=0 >> jin-11 127.0.0.1 | CHANGED | rc=0 >> jin-10
3.2 ansible拷贝文件或目录
ansible拷贝目录时,如果目标指定的目录不存在,它会自动创建: [root@jin-10 ~]# ansible jin-11 -m copy -a "src=/etc/ansible dest=/tmp/ansible_test owner=root group=root mode=0755" jin-11 | CHANGED => { "changed": true, "dest": "/tmp/ansible_test/", "src": "/etc/ansible" } [root@jin-11 ~]# ll /tmp/ansible_test 总用量 0 drwxr-xr-x. 3 root root 51 9月 4 09:24 ansible
可以看到目标目录被创建且源目录在目标目录下面。
操作时,要注意src和dest是文件还是目录,拷贝文件时,如果目标目录已经存在同名目录,那么会把该文件放到同名目录里面: [root@jin-11 /tmp]# ll -t 总用量 4 drwxr-xr-x. 2 root root 6 9月 4 09:32 m_test [root@jin-10 ~]# ansible jin-11 -m copy -a "src=/etc/passwd dest=/tmp/m_test" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "1db31b171f911344344ceb7cf8621d31be83ec07", "dest": "/tmp/m_test/passwd", "gid": 0, "group": "root", "md5sum": "e1a0ce587167dc3587ee5f1b357dc3c4", "mode": "0644", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 1299, "src": "/root/.ansible/tmp/ansible-tmp-1567560870.7-110358489604181/source", "state": "file", "uid": 0 } [root@jin-11 /tmp]# ls m_test/ passwd
3.3 ansible远程执行脚本
在jin-10上编写一个test_ansible.sh的脚本,内容如下: #!/bin/bash echo `date` >/tmp/test_ansible.txt
然后分发到主机组testhost: [root@jin-10 ~]# ansible testhost -m copy -a "src=/tmp/test_ansible.sh dest=/tmp/test.sh mode=0755" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "0dc6471ba363889d83d3bf98c4354d874c86bd19", "dest": "/tmp/test.sh", "gid": 0, "group": "root", "md5sum": "bfee9509a7066ca2e822dbe48b21fbe7", "mode": "0755", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 48, "src": "/root/.ansible/tmp/ansible-tmp-1567561704.9-93797175448931/source", "state": "file", "uid": 0 } 127.0.0.1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "0dc6471ba363889d83d3bf98c4354d874c86bd19", "dest": "/tmp/test.sh", "gid": 0, "group": "root", "md5sum": "bfee9509a7066ca2e822dbe48b21fbe7", "mode": "0755", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 48, "src": "/root/.ansible/tmp/ansible-tmp-1567561704.82-59120276764010/source", "state": "file", "uid": 0 }
然后批量执行该脚本: [root@jin-10 ~]# ansible testhost -m shell -a "/tmp/test.sh" jin-11 | CHANGED | rc=0 >> 127.0.0.1 | CHANGED | rc=0 >> [root@jin-10 ~]# ls /tmp/test_ansible.txt /tmp/test_ansible.txt [root@jin-10 ~]# cat !$ cat /tmp/test_ansible.txt 2019年 09月 04日 星期三 09:50:23 CST [root@jin-11 /tmp]# ls /tmp/test_ansible.txt /tmp/test_ansible.txt [root@jin-11 /tmp]# cat !$ cat /tmp/test_ansible.txt 2019年 09月 04日 星期三 09:50:23 CST
3.4 ansible管理任务计划
使用cron模块可以创建计划任务: [root@jin-10 ~]# ansible jin-11 -m cron -a "name='test cron' job='/bin/touch /tmp/test_cron' weekday=6" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "envs": [], "jobs": [ "test cron" ] } [root@jin-11 /tmp]# crontab -l #Ansible: test cron * * * * 6 /bin/touch /tmp/test_cron
使用cron模块删除计划任务: [root@jin-10 ~]# ansible jin-11 -m cron -a "name='test cron' state=absent" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "envs": [], "jobs": [] } [root@jin-11 /tmp]# crontab -l
可以看到,之前创建的计划任务已经被删除了。
3.5 ansible安装包和管理服务
ansible的yum模块可以安装包和管理服务(可以进行安装包的安装、卸载和远程启动等)。 我们在jin-11上安装httpd为例: [root@jin-11 ~]# rpm -qa httpd [root@jin-11 ~]#
可以看到,jin-11是没有安装httpd的。
在jin-10上执行如下命令: [root@jin-10 ~]# ansible jin-11 -m yum -a "name=httpd state=installed" jin-11 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "changes": { "installed": [ "httpd" ] }, "msg": "", "rc": 0, "results": [ "Loaded plugins: fastestmirror\nLoading mirror speeds from cached hostfile\n * base: mirrors.tuna.tsinghua.edu.cn\n * epel: hkg.mirror.rackspace.com\n * extras: mirrors.aliyun.com\n * updates: mirrors.tuna.tsinghua.edu.cn\nResolving Dependencies\n--> Running transaction check\n---> Package httpd.x86_64 0:2.4.6-89.el7.centos.1 will be installed\n--> Processing Dependency: httpd-tools = 2.4.6-89.el7.centos.1 for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-89.el7.centos.1.x86_64\n--> Running transaction check\n---> Package apr.x86_64 0:1.4.8-3.el7_4.1 will be installed\n---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed\n---> Package httpd-tools.x86_64 0:2.4.6-89.el7.centos.1 will be installed\n---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package Arch Version Repository Size\n================================================================================\nInstalling:\n httpd x86_64 2.4.6-89.el7.centos.1 updates 2.7 M\nInstalling for dependencies:\n apr x86_64 1.4.8-3.el7_4.1 base 103 k\n apr-util x86_64 1.5.2-6.el7 base 92 k\n httpd-tools x86_64 2.4.6-89.el7.centos.1 updates 91 k\n mailcap noarch 2.1.41-2.el7 base 31 k\n\nTransaction Summary\n================================================================================\nInstall 1 Package (+4 Dependent packages)\n\nTotal download size: 3.0 M\nInstalled size: 10 M\nDownloading packages:\n--------------------------------------------------------------------------------\nTotal 260 kB/s | 3.0 MB 00:11 \nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n Installing : apr-1.4.8-3.el7_4.1.x86_64 1/5 \n Installing : apr-util-1.5.2-6.el7.x86_64 2/5 \n Installing : httpd-tools-2.4.6-89.el7.centos.1.x86_64 3/5 \n Installing : mailcap-2.1.41-2.el7.noarch 4/5 \n Installing : httpd-2.4.6-89.el7.centos.1.x86_64 5/5 \n Verifying : httpd-2.4.6-89.el7.centos.1.x86_64 1/5 \n Verifying : mailcap-2.1.41-2.el7.noarch 2/5 \n Verifying : httpd-tools-2.4.6-89.el7.centos.1.x86_64 3/5 \n Verifying : apr-util-1.5.2-6.el7.x86_64 4/5 \n Verifying : apr-1.4.8-3.el7_4.1.x86_64 5/5 \n\nInstalled:\n httpd.x86_64 0:2.4.6-89.el7.centos.1 \n\nDependency Installed:\n apr.x86_64 0:1.4.8-3.el7_4.1 apr-util.x86_64 0:1.5.2-6.el7 \n httpd-tools.x86_64 0:2.4.6-89.el7.centos.1 mailcap.noarch 0:2.1.41-2.el7 \n\nComplete!\n" ] }
再在jin-11上查看,已经安装了httpd包,并且没有启动: [root@jin-11 ~]# rpm -qa httpd httpd-2.4.6-89.el7.centos.1.x86_64 [root@jin-11 ~]# ps aux |grep httpd root 4256 0.0 0.1 112724 996 pts/0 S+ 11:29 0:00 grep --color=auto httpd
我们再远程启动httpd: [root@jin-10 ~]# ansible jin-11 -m service -a "name=httpd state=started enabled=no"
再查看jin-11是否成功启动httpd服务: [root@jin-11 ~]# ps aux |grep httpd root 4355 19.0 0.5 230408 5184 ? Ss 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4356 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4357 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4358 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4360 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND apache 4361 0.0 0.3 230408 3004 ? S 11:31 0:00 /usr/sbin/httpd -DFOREGROUND root 4370 0.0 0.1 112724 996 pts/0 R+ 11:31 0:00 grep --color=auto httpd
可以看到,httpd服务已经成功启动。
4、ansible playbook
playbook是指一个可被ansible执行的yml文件,最基本的playbook脚本分为三个部分:
1、在什么机器上以什么身份执行: hosts:为主机的IP,或者主机组名,或者关键字all。 users:在远程以哪个用户身份执行。 ...
2、执行的任务有哪些: tasks tasks是从上到下顺序执行,如果中间发生错误,那么整个playbook会中止。我们可以改修文件后,再重新执行。 每一个task都是对module的一次调用,只是使用不同的参数和变量而已。 每一个task最好有name属性(可选),这个是供人读的,没有实际的操作。然后会在命令行里面输出,提示用户执行情况。 task中每个action会调用一个module,在module中会去检查当前系统状态是否需要重新执行。 如果本次执行了,那么action会得到返回值changed。 如果不需要执行,那么action得到返回值ok。
3、善后的任务有哪些: handlers handler也是对module的一次调用,与tasks不同,tasks会默认的按定义顺序执行每一个task,handlers则不会,它需要在tasks中被调用,才有可能被执行。 handler可以理解为playbook的event,在ansible中,只在task的执行状态为changed的时候,才会执行该task调用的handler,这也是handler与普通的event机制不同的地方。 如果有多个task notify同一个handler,那么只执行一次。 handler的使用场景:例如执行task之后,服务器状态发生变化之后要执行的一些操作,比如我们修改了配置文件后,需要重启一下服务,这时候就可以用handler模块。
下面我们来编写一个简单的playbook,vim /etc/ansible/test.yml: --- - hosts: jin-11 remote_user: root tasks: - name: test_playbook shell: touch /tmp/test_playbook.txt
然后执行: [root@jin-10 /etc/ansible]# ansible-playbook /etc/ansible/test.yml PLAY [jin-11] ************************************************************************ TASK [Gathering Facts] *************************************************************** ok: [jin-11] TASK [test_playbook] ***************************************************************** [WARNING]: Consider using the file module with state=touch rather than running 'touch'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@jin-11 ~]# ll -t /tmp/ 总用量 16 -rw-r--r--. 1 root root 0 9月 4 14:49 test_playbook.txt
可以看到,在jin-11的/tmp/下生成了文件test_playbook.txt。
4.1 playbook的变量 自定义变量
在playbook中,通过vars关键字自定义变量,使用时用{{ }}引用以来即可。
我们以创建用户为例,编辑vim /etc/ansible/create_user.yml,内容如下: --- - name: create_user hosts: jin-11 user: root gather_facts: false vars: - user: "test" tasks: - name: create user user: name="{{ user }}"
然后执行: [root@jin-10 /etc/ansible]# ansible-playbook /etc/ansible/create_user.yml PLAY [create_user] ******************************************************************* TASK [create user] ******************************************************************* changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在jin-11上查看: [root@jin-11 ~]# id test uid=1002(test) gid=1002(test) 组=1002(test) 主机的系统变量(facts)
ansible会通过module setup来收集主机的系统信息,这些收集到的系统信息叫做facts,这些facts信息可以直接以变量的形式使用。 [root@jin-10 ~]# ansible all -m setup -u root jin-11 | SUCCESS => { "ansible_facts": { "ansible_all_ipv4_addresses": [ "192.168.154.11" ], "ansible_all_ipv6_addresses": [ "fe80::c9a8:961e:9b39:d180" ], "ansible_apparmor": { "status": "disabled" }, "ansible_architecture": "x86_64", ... 文件模板中使用的变量
在playbook中定义的变量,可以直接在template中使用,facts变量也可以直接在template中使用,包含在inventory里面定义的host和group变量。只要是在playbook中可以访问的变量,都可以在template文件中使用。 把运行结果当做变量使用-注册变量
把task的执行结果也可以作为一个变量值。这个时候就需要用到“注册变量”,将执行结果注册到一个变量中,待后面的action使用,注册变量经常和debug module一起使用,这样可以得到更多action的输出信息,帮助用户调试。 --- - hosts: all tasks: - shell: ls register: result ignore_errors: True - shell: echo "{{ result.stdout }}" when: result.rc == 5 - debug: msg="{{ result.stdout }}" 用命令行传递参数
为了使Playbook更灵活、通用性更强,允许用户在执行的时候传入变量的值,这个时候就需要用到“额外变量”。
在playbook中定义的变量,需要从命令行传递变量值。
下面是在命令行里面传值得的方法: [root@jin-10 ~]# ansible-playbook for_var_test.yml --extra-vars "hosts=jin-11 user=root"
可以用json格式传递参数: [root@jin-10 ~]# ansible-playbook for_var_test.yml --extra-vars "{ 'hosts': 'jin-11', 'user': 'root' }"
还可以把参数放在文件里面: [root@jin-10 ~]# ansible-playbook for_var_test.yml --extra-vars "@vars.json"
4.2 playbook中的条件判断
when: 类似于编程语言中的if语句
编辑文件vim /etc/ansible/when.yml,内容如下: --- - hosts: testhost user: root gather_facts: True tasks: - name: use when shell: touch /tmp/when.txt when: ansible_ens32.ipv4.address == "192.168.154.11"
执行: [root@jin-10 ~]# ansible-playbook /etc/ansible/when.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [use when] ********************************************************************** skipping: [127.0.0.1] [WARNING]: Consider using the file module with state=touch rather than running 'touch'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] PLAY RECAP *************************************************************************** 127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 jin-11 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
查看结果: [root@jin-10 ~]# ll -t /tmp/ 总用量 12 -rw-r--r--. 1 root root 43 9月 4 09:50 test_ansible.txt -rwxr-xr-x. 1 root root 48 9月 4 09:48 test.sh [root@jin-11 ~]# ll -t /tmp/ 总用量 16 -rw-r--r--. 1 root root 0 9月 5 09:33 when.txt -rw-r--r--. 1 root root 0 9月 4 14:49 test_playbook.txt
可以看到jin-10没有生成when.txt文件,而jin-11则生成了。
4.3 playbook循环
loop: 类似于编程语言中的while语句,在playbook脚本中一般写作with_...
编辑文件vim /etc/ansible/while.yml,内容如下: --- - hosts: testhost user: root tasks: - name: change mode for files file: path=/tmp/{{ item }} state=touch mode=600 with_items: - while1.txt - while2.txt - while3.txt
执行: [root@jin-10 ~]# ansible-playbook /etc/ansible/while.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [change mode for files] ********************************************************* changed: [jin-11] => (item=while1.txt) changed: [127.0.0.1] => (item=while1.txt) changed: [jin-11] => (item=while2.txt) changed: [jin-11] => (item=while3.txt) changed: [127.0.0.1] => (item=while2.txt) changed: [127.0.0.1] => (item=while3.txt) PLAY RECAP *************************************************************************** 127.0.0.1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
查看结果: [root@jin-10 ~]# ll -t /tmp/ 总用量 12 -rw-------. 1 root root 0 9月 5 09:45 while3.txt -rw-------. 1 root root 0 9月 5 09:45 while2.txt -rw-------. 1 root root 0 9月 5 09:45 while1.txt -rw-r--r--. 1 root root 43 9月 4 09:50 test_ansible.txt [root@jin-11 ~]# ll -t /tmp/ 总用量 16 -rw-------. 1 root root 0 9月 5 09:45 while3.txt -rw-------. 1 root root 0 9月 5 09:45 while2.txt -rw-------. 1 root root 0 9月 5 09:45 while1.txt -rw-r--r--. 1 root root 0 9月 5 09:33 when.txt
4.4 用playbook安装nginx
思路:先在一台机器上编译安装好nginx、打包,然后再用ansible去下发。
- 创建相关的目录
进入ansible配置文件目录, 创建一个nginx_install的目录,方便管理,最终目录如下: [root@jin-10 /var/lib/docker]# cd /etc/ansible/ [root@jin-10 /etc/ansible]# mkdir nginx_install [root@jin-10 /etc/ansible]# cd !$ cd nginx_install [root@jin-10 /etc/ansible/nginx_install]# mkdir -p roles/{common,install}/{handlers,files,meta,tasks,templates,vars} [root@jin-10 /etc/ansible/nginx_install]# tree . └── roles ├── common │ ├── files │ ├── handlers │ ├── meta │ ├── tasks │ ├── templates │ └── vars └── install ├── files ├── handlers ├── meta ├── tasks ├── templates └── vars
说明:roles目录下有两个角色,common为一些准备操作,install为安装nginx的操作。
每个角色下面又有几个目录,handlers下面是当发生改变时要执行的操作,通常用在配置文件发生改变,重启服务。files为安装时用到的一些文件,meta为说明信息,说明角色依赖等信息,tasks里面是核心的配置文件,templates通常存一些配置文件,启动脚本等模板文件,vars下为定义的变量。 准备相关的文件
之前已经在jin-10上编译安装了nginx,相关目录文件如下: nginx的目录: [root@jin-10 /etc/ansible/nginx_install]# ls /usr/local/nginx/ client_body_temp fastcgi_temp logs sbin uwsgi_temp conf html proxy_temp scgi_temp nginx的启动脚本: [root@jin-10 /etc/ansible/nginx_install]# ls /etc/init.d/nginx /etc/init.d/nginx nginx的配置文件: [root@jin-10 /etc/ansible/nginx_install]# ls /usr/local/nginx/conf/nginx.conf /usr/local/nginx/conf/nginx.conf
把nginx的目录打包,并复制文件到之前创建的目录中: [root@jin-10 /usr/local]# tar czf nginx.tar.gz --exclude "nginx.conf" --exclude "vhost" nginx/ [root@jin-10 /usr/local]# mv nginx.tar.gz /etc/ansible/nginx_install/roles/install/files/ [root@jin-10 /usr/local]# cp nginx/conf/nginx.conf /etc/ansible/nginx_install/roles/install/templates/ [root@jin-10 /usr/local]# cp /etc/init.d/nginx /etc/ansible/nginx_install/roles/install/templates/
定义common的tasks,安装nginx需要的一些依赖包:
进入/etc/ansible/nginx_install/roles/目录,并编写/etc/ansible/nginx_install/roles/common/tasks/main.yml文件,内容如下: [root@jin-10 /etc/ansible/nginx_install/roles]# vim /etc/ansible/nginx_install/roles/common/tasks/main.yml - name: Install initializtion require software yum: name={{ item }} state=installed with_items: - zlib-devel - pcre-devel
再编写一个定义变量的文件vim /etc/ansible/nginx_install/roles/install/vars/main.yml,内容如下: nginx_user: www nginx_port: 80 nginx_basedir: /usr/local/nginx
然后再创建一个配置文件vim /etc/ansible/nginx_install/roles/install/tasks/copy.yml ,用于拷贝文件到目标机器: - name: Copy nginx software copy: src=nginx.tar.gz dest=/tmp/nginx.tar.gz owner=root group=root - name: Uncompression nginx software shell: tar zxf /tmp/nginx.tar.gz -C /usr/local/ - name: Copy nginx start script template: src=nginx dest=/etc/init.d/nginx owner=root group=root mode=0755 - name: Copy nginx config template: src=nginx.conf dest={{ nginx_basedir }}/conf/ owner=root group=root mode=06 44
再创建一个文件vim /etc/ansible/nginx_install/roles/install/tasks/install.yml ,用于建立用户,启动服务,删除压缩包: - name: Create nginx user user: name={{ nginx_user }} state=present createhome=no shell=/sbin/nologin - name: Start nginx service shell: /etc/init.d/nginx start - name: Add boot start nginx service shell: chkconfig --level 345 nginx on - name: Delete nginx compression files shell: rm -rf /tmp/nginx.tar.gz
再创建一个调用copy和install的文件 /etc/ansible/nginx_install/roles/install/tasks/main.yml,内容如下: - include: copy.yml - include: install.yml
最后,我们再定义一个入口配置文件/etc/ansible/nginx_install/install.yml,内容如下: --- - hosts: jin-11 remote_user: root gather_facts: True roles: - common - install
最终目录文件如下: [root@jin-10 /etc/ansible/nginx_install]# tree . ├── install.yml └── roles ├── common │   ├── files │   ├── handlers │   ├── meta │   ├── tasks │   │   └── main.yml │   ├── templates │   └── vars └── install ├── files │   └── nginx.tar.gz ├── handlers ├── meta ├── tasks │   ├── copy.yml │   ├── install.yml │   └── main.yml ├── templates │   ├── nginx │   └── nginx.conf └── vars └── main.yml 执行脚本: [root@jin-11 ~]# yum remove nginx 已加载插件:fastestmirror 参数 nginx 没有匹配 不删除任何软件包
如果目标机器上已安装了nginx,先用命令yum remove nginx删除已安装的nginx。
然后执行playbook脚本: [root@jin-10 /etc/ansible/nginx_install]# ansible-playbook /etc/ansible/nginx_install/install.yml PLAY [jin-11] ************************************************************************ TASK [Gathering Facts] *************************************************************** ok: [jin-11] TASK [common : Install initializtion require software] ******************************* [DEPRECATION WARNING]: Invoking "yum" only once while using a loop via squash_actions is deprecated. Instead of using a loop to supply multiple items and specifying `name: "{{ item }}"`, please use `name: ['zlib-devel', 'pcre-devel']` and remove the loop. This feature will be removed in version 2.11. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. ok: [jin-11] => (item=[u'zlib-devel', u'pcre-devel']) TASK [install : Copy nginx software] ************************************************* changed: [jin-11] TASK [install : Uncompression nginx software] **************************************** [WARNING]: Consider using the unarchive module rather than running 'tar'. If you need to use command because unarchive is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] TASK [install : Copy nginx start script] ********************************************* ok: [jin-11] TASK [install : Copy nginx config] *************************************************** ok: [jin-11] TASK [install : Create nginx user] *************************************************** changed: [jin-11] TASK [install : Start nginx service] ************************************************* changed: [jin-11] TASK [install : Add boot start nginx service] **************************************** changed: [jin-11] TASK [install : Delete nginx compression files] ************************************** [WARNING]: Consider using the file module with state=absent rather than running 'rm'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=10 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在jin-11主机上查看是否有nginx的进程: [root@jin-11 ~]# ps aux|grep nginx root 4040 0.0 0.0 20540 628 ? Ss 08:32 0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf nobody 4042 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process nobody 4043 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process root 4192 0.0 0.1 112724 996 pts/0 S+ 09:45 0:00 grep --color=auto nginx
4.5 playbook管理配置文件
安装软件一般是在初始化环境的时候,而在生产环境中,我们大多数是要管理配置文件。
下面我们来写一个管理nginx配置文件的playbook。
首先先创建所需要的目录: [root@jin-10 ~]# mkdir -p /etc/ansible/nginx_config/roles/{new,old}/{files,handlers,vars,tasks}
最终目录如下: [root@jin-10 ~]# tree /etc/ansible/nginx_config/ /etc/ansible/nginx_config/ └── roles ├── new │   ├── files │   ├── handlers │   ├── tasks │   └── vars └── old ├── files ├── handlers ├── tasks └── vars
其中,new目录更新时用到,old回滚时用到,files下面为nginx.conf,handlers为重启nginx服务的命令。
关于回滚,需要在执行playbook之前先备份旧的配置,所以对于旧的配置文件的管理一定要谨慎,千万不要随便去修改线上机器的配置,而且要保证new/files下面的配置和线上的配置保持一致。
拷贝nginx.conf文件到/etc/ansible/nginx_config/roles/new/files/目录下: [root@jin-10 /usr/local/nginx/conf]# cp -r nginx.conf /etc/ansible/nginx_config/roles/new/files/
编辑一个定义变量的文件vim /etc/ansible/nginx_config/roles/new/vars/main.yml,内容如下: nginx_basedir: /usr/local/nginx
再编写一个重新加载nginx服务的文件vim /etc/ansible/nginx_config/roles/new/handlers/main.yml,内容如下: - name: restart nginx shell: /etc/init.d/nginx reload
再编写一个核心任务的文件vim /etc/ansible/nginx_config/roles/new/tasks/main.yml ,内容如下: - name: copy conf file copy: src={{ item.src }} dest={{ nginx_basedir }}/{{ item.dest }} backup=yes owner=r oot group=root mode=0644 with_items: - { src: nginx.conf, dest: conf/nginx.conf } notify: restart nginx
最后,我们定义一个总入口配置文件vim /etc/ansible/nginx_config/update.yml,内容如下: --- - hosts: testhost user: root roles: - new
执行update.yml [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/update.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [new : copy conf file] ********************************************************** ok: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) ok: [127.0.0.1] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) PLAY RECAP *************************************************************************** 127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看到,nginx服务已经重新加载了: [root@jin-11 ~]# ps aux|grep nginx root 4040 0.0 0.0 20540 628 ? Ss 08:32 0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf nobody 4042 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process nobody 4043 0.0 0.3 22984 3192 ? S 08:32 0:00 nginx: worker process root 4570 0.0 0.1 112724 996 pts/0 S+ 10:43 0:00 grep --color=auto nginx [root@jin-11 ~]# date 2019年 09月 09日 星期一 10:44:12 CST
我们编辑nginx.conf,加上一行注释内容:# test for the rollback,然后再执行update.yml脚本: [root@jin-10 ~]# cat /etc/ansible/nginx_config/roles/new/files/nginx.conf|grep test # test for the rollback [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/update.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [new : copy conf file] ********************************************************** ok: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) ok: [127.0.0.1] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) PLAY RECAP *************************************************************************** 127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在jin-11上查看nginx.conf是否备份成功: [root@jin-11 ~]# cat /usr/local/nginx/conf/nginx.conf|grep test # test for the rollback
以上的操作是针对于更新、修改配置文件的,下面来介绍一下回滚操作:
回滚操作就是把旧的配置覆盖,然后重新加载nginx服务, 每次改动nginx配置文件之前先备份到old里,对应目录为/etc/ansible/nginx_config/roles/old/files。
拷贝new目录里的文件到old目录里,相当于把当前nginx配置文件备份到old里,如需回滚就将备份还原。 [root@jin-10 ~]# rsync -av /etc/ansible/nginx_config/roles/new/ /etc/ansible/nginx_config/roles/old/ sending incremental file list files/ files/nginx.conf handlers/ handlers/main.yml tasks/ tasks/main.yml vars/ vars/main.yml sent 2,535 bytes received 108 bytes 5,286.00 bytes/sec total size is 2,080 speedup is 0.79
编写一个总入口配置文件vim /etc/ansible/nginx_config/rollback.yml,内容如下: --- - hosts: jin-11 user: root roles: - old
下面我们来进行一个简单的回滚操作:
1、先将配置文件同步到old目录下: [root@jin-10 ~]# rsync -av /etc/ansible/nginx_config/roles/new/files/nginx.conf /etc/ansible/nginx_config/roles/old/files/nginx.conf sending incremental file list sent 49 bytes received 12 bytes 122.00 bytes/sec total size is 1,772 speedup is 29.05
2、修改配置文件/etc/ansible/nginx_config/roles/new/files/nginx.conf,文件末尾增加一行注释内容: #test the ansible playbook rollback
3、执行update.yml文件向客户端更新文件: [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/update.yml PLAY [testhost] ********************************************************************** TASK [Gathering Facts] *************************************************************** ok: [jin-11] ok: [127.0.0.1] TASK [new : copy conf file] ********************************************************** changed: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) changed: [127.0.0.1] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) RUNNING HANDLER [new : restart nginx] ************************************************ changed: [jin-11] changed: [127.0.0.1] PLAY RECAP *************************************************************************** 127.0.0.1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 jin-11 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4、在jin-11上查看,文件是否同步: [root@jin-11 ~]# tail -n1 /usr/local/nginx/conf/nginx.conf #test the ansible playbook rollback
5、可以看到,同步成功,然后我们执行rollback.yml文件进行回滚操作: [root@jin-10 ~]# ansible-playbook /etc/ansible/nginx_config/rollback.yml PLAY [jin-11] ************************************************************************ TASK [Gathering Facts] *************************************************************** ok: [jin-11] TASK [old : copy conf file] ********************************************************** changed: [jin-11] => (item={u'dest': u'conf/nginx.conf', u'src': u'nginx.conf'}) RUNNING HANDLER [old : restart nginx] ************************************************ changed: [jin-11] PLAY RECAP *************************************************************************** jin-11 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
6、在jin-11上查看是否已恢复: [root@jin-11 ~]# tail -n1 /usr/local/nginx/conf/nginx.conf } [root@jin-11 ~]#
可以看到,之前文件末尾增加的那一行没有了。
所以,所谓的回滚就是在操作改动前,先把文件备份一份,如果出现误操作,就将之前备份的文件还原回去,这样就起到了一个回滚的效果。
系统运维
2019-10-15 11:32:00
手动设置系统时间: sudo date -s "2019-10-12 22:31:00" 将硬件时间改为当地时间,并将系统时间同步到硬件时钟上: sudo hwclock --localtime --systohc
系统运维
2019-10-12 23:36:00
Docker
Docker是一个为开发者和运维工程师(系统管理员)以容器的方式构建,分享和运行应用的平台.使用容器进行应用部署的方式,我们成为容器化.
容器化应用具有一下特性,使得容器化日益流行: 灵活: 再复杂的应用都可以进行容器化. 轻量: 容器使用使用和共享主机的内核,在系统资源的利用比虚拟机更加高效. 可移植: 容器可以本地构建,部署到云上,运行在任何地方. 松耦合: 容器是高度自封装的,可以在不影响其他容器的情况下替换和升级容器. 可扩展: 可以在整个数据中心里增加和自动分发容器副本. 安全: 容器约束和隔离应用进程,而无需用户进行任何配置.
镜像和容器
其实,容器就是运行的进程,附带一些封装的特性,使其与主机上和其他容器的进程隔离.每个容器都只访问它自己私有的文件系统,这是容器隔离很重要的一方面.而Docker镜像就提供了这个文件系统,一个镜像包含运行该应用所有需求 - 代码或者二进制文件,运行时,依赖库,以及其他需要的文件系统对象 通过与虚拟机对比,虚拟机(VM)通过一个虚拟机管理(Hypervisor)运行了完整的操作系统来访问主机资源.通常虚拟机会产生大量的开销,超过了应用本身所需要的开销.
容器编排
容器化过程的可移植性和可重复性意味着我们有机会跨云和数据中心移动和扩展容器化的应用程序,容器有效地保证应用程序可以在任何地方以相同的方式运行,这使我们可以快速有效地利用所有这些环境.当我们扩展我们的应用,我们需要一些工具来帮助自动维护这些应用,在容器的生命周期里,可以自动替换失败的容器,管理滚动升级,以及重新配置.  
容器编排器(Orchestrator)就是管理,扩展和维护容器化应用的工具,当前最常见的例子就是 Kubernetes 和 Docke Swarm . Docker Desktop 工具可以在开发环境提供这两个编排工具.当前, Docker Desktop 仅支持在 Windows 和 OSX 系统上安装,本文接下来主要介绍如何在 Linux 上安装Docker,以及运行一个容器.
安装Docker
如果你使用的是Windows或者Mac OS系统,请参考上面的链接安装和使用 Docker Desktop ,下面我们将已 Ubuntu 18.04 系统为例来安装Docker的社区版本(docker-ce).  
配置软件源 更新 apt 包索引 $ sudo apt update 安装需要的软件包 $ sudo apt install \ apt-transport-https \ ca-certificates \ curl \ gnupg-agent \ software-properties-common 添加Docker官方的GPG信息 $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 使用如下命令添加Docker的安装源 $ sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable"
stable 意思为添加的是稳定版本的源.
安装 更新软件源 $ sudo apt update 安装最新版本的Docker引擎和containerd $ sudo apt install docker-ce docker-ce-cli containerd.io 验证安装 $ sudo docker run hello-world
该命令会下载一个测试镜像并运行一个容器,该容器会打印一些信息后退出.  
补充 : 为了运行 docker 命令不需要使用 sudo ,可以将当前用户加入 docker $ sudo usermod -aG "docker"
在重新登录用户后,可以直接使用 docker 命令进行操作.
运行容器
在上面安装 docker 引擎的最后一个验证步骤中,其实我们已经运行了一个容器,这里再示例如何运行一个 ubuntu 操作系统的基础容器.
以下命令假设已经将当前用户添加到了 docker 组. 拉取(Pull)镜像 $ docker image pull ubuntu:18.04
该命令将从Docker官方的镜像仓库( Docker Hub )上下载一个官方维护的Ubuntu的基础镜像,标签为 18.04 .   运行容器 $ docker container run --rm -it ubuntu:18.04 root@451a1f6ed7c9:/#
上面的命令,将用 ubuntu:18.04 的镜像运行一个容器, --rm 选项指示在退出或停止容器时自动删除该容器, -it 选项表示创建一个tty并与其交互,因为该容器默认运行的进程是 bash ,这样我们可直接在容器里进行交互.
在容器运行时,可以打开另一个终端,使用如下命令查看当前运行在该主机上的容器 docker container ls CONTAINER ID NAMES IMAGE CREATED ago STATUS PORTS COMMAND 451a1f6ed7c9 musing_diffie ubuntu:18.04 4 minutes ago ago Up 4 minutes "/bin/bash"
当我们在容器的只能高端里执行 exit ,或者在主机的另一个终端里执行 docker container stop musing_diffie ,都将停止并删除该容器.
总结
这里我们介绍了容器的基本概念,以及在 Ubuntu 系统上安装Docker,运行了一个简单的容器.该系列接下来的文章中我会介绍如何容器化我们的应用程序.
系统运维
2020-02-14 11:17:00
QinQ 是 802.1Q in 802.1Q 的简称,是基于 IEEE 802.1Q 技术的一种比较简单的二层 VPN 协议。
QinQ简介
IEEE 802.1Q 定义的 VLAN ID 域有 12 个比特,最多可以提供 4094 个 VLAN。但在实际应用中,尤其是在城域网中,需要大量的 VLAN 来隔离用户,4094 个 VLAN 远远不能满足需求。QinQ 使整个网络最多可以提供 4094×4094 个 VLAN,满足了城域网对 VLAN 数量的需求。
QinQ 是 802.1Q in 802.1Q 的简称,是基于 IEEE 802.1Q 技术的一种比较简单的二层 VPN 协议。
通过将一层 VLAN Tag 封装到私网报文上,使其携带两层 VLAN Tag 穿越运营商的骨干网络(又称公网),从而使运营商能够利用一个 VLAN 为包含多个 VLAN 的用户网络提供服务。
QinQ 具备以下优点
缓解公网 VLAN 资源日益紧缺的问题。
用户可以规划自己的私网 VLAN,不会导致与公网 VLAN 冲突。
为用户提供了一种简单、灵活的二层 VPN 解决方案。
当运营商进行 VLAN 规划时,用户网络不必更改原有配置,使用户网络具有了较强的独立性。
QinQ的工作原理
QinQ报文在运营商网络中传输时带有双层VLAN Tag:
内层 VLAN Tag:为用户的私网 VLAN Tag,Customer VLAN Tag (简称 CVLAN)。设备依靠该 Tag 在私网中传送报文。
外层 VLAN Tag:为运营商分配给用户的公网 VLAN Tag, Service VLAN Tag(简称 SVLAN)。设备依靠该 Tag 在公网中传送 QinQ 报文。
在公网的传输过程中,设备只根据外层 VLAN Tag 转发报文,而内层 VLAN Tag 将被当作报文的数据部分进行传输。
QinQ网络拓扑
用户网络A和B的私网VLAN分别为VLAN 1~10 和VLAN 1~20。运营商为用户网络A和B分配的公网VLAN分别为VLAN 3 和VLAN 4。
当用户网络 A 和 B 中带私网 VLAN Tag 的报文进入运营商网络时,报文外面就会被分别封装上 VLAN 3 和 VLAN 4 的公网 VLAN Tag。
来自不同用户网络的报文在运营商网络中传输时被隔离,即使这些用户网络各自的 VLAN 范围存在重叠,因为分配到的公网 VLAN 不同,在运营商网络中传输时也不会产生冲突。
当报文穿过运营商网络,到达运营商网络另一侧 PE(Provider Edge,服务提供商网络边缘)设备后,报文被剥离公网 VLAN Tag,然后再传送给用户网络的 CE(Customer Edge,用户网络边缘)设备。
QinQ的实现方式
当端口上配置了 QinQ 功能后,不论从该端口收到的报文是否带有 VLAN Tag,设备都会为该报文添加本端口缺省 VLAN 的 Tag:
如果收到的是带有 VLAN Tag 的报文,该报文就成为带两层 Tag 的报文;
如果收到的是不带 VLAN Tag 的报文,该报文就成为带有本端口缺省 VLAN Tag 的报文。 本文地址: https://www.linuxprobe.com/qinq-vlan-tag.html
系统运维
2020-02-13 12:15:00
这篇文章主要介绍了php pdo连接数据库操作,结合实例形式分析了PHP使用pdo连接数据库并执行事务相关操作技巧,需要的朋友可以参考下
pdo连接数据库的有点是能实现不同数据库之间的转换,而且有事务功能的回滚,更有pdo::prepare();pdo:::execute()函数的预处理查询,所以我个人认为pdo的功能还是比较强大的,所有这篇日志只为我自己而写,希望看到这篇日志的兄弟们能对你们有所帮助。
要用php连接数据库首先要要实例化pdo的类,并且要有数据源,服务器账号,服务器密码
数据源是数据库类型,服务器名称,数据库名称的一个集合。 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 ?>
事务介绍:事务介绍我就通过我自己的理解来讲解一遍吧,就是先要关闭数据库的自动提交功能(什么是自动提交功能?就是当我们写完一个sql语句后,按回车键执行不起,而要经过特殊的代码处理才能提交上去,后面我会介绍的)
然后写出你要执行的sql语句并将返回的结果赋给两个不同的变量,之后提交,如果在执行的时候其中1个或多个发生了错误,就进行事务回滚,即使回归初始状态(也就是前面在事务处理代码中的插入或改变或删除或查询的语句全部作废),还有一个优点是不会因为进入其他网页,或执行其他sql语句而影响到事务处理的进程 //以下是事务回滚的代码简介 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 $pdo->begintransaction();//在这里关闭mysql的自动提交功能 $a=$pdo->query("insert into tongxue values('130042106','谭勇','男'); $b=$pdo->query("insert into tongxue values('130042100','猪八戒','男')"); if($a==true && $b==true){ $pdo->commit();//提交事务 } else{ $pdo->rollback();//事务回滚 } ?>
用mysql_num_rows()函数能数出数据库返回结果集的行数,以此来判断该用户输入的用户名和密码是否正确,那么在pdo中我们如何实现这个功能呢?
在pdo中有一个函数pdo::fetchall(),他的作用是将从数据库返回的一个结果集全部赋给获取它的值,之后再用count()函数数出行数具体事例代码如下 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 $sql="select * from tongxue where id='130042106'"; $shuju=pdo->prepare($sql);//这就是我们所说的预处理 $shuju->execute();//执行预处理的结果; $jg=$shuju->fetchall(PDO::FETCH_ASSOC);//将返回的结果集以数组的方式全部返回给变量$jg $hangshu=count($jg);//数出结果集的行数 if($hangshu>0){ echo '查询出来是有这个人的'; } else{ echo '查询出来是没有这个人的'; } ?>
当我们的页面运行的sql语句较多时,可以用pdo当中的预处理,来减缓服务器的压力,这对于那些要做大型网站的项目来说是一个不错的选择,因为大型网站一天的浏览量是几万或十几万的.
下面我们来看看pdo的预处理 query("set names gbk");//设置从数据库里面传递过来的数据的编码格式 $sql="insert into tongxue values('130042100','老师','男')";//我们要执行的sql语句 $shuju=$pdo->prepare($sql); //预处理 $shuju->execute();//执行预处理的sql语句 if($shuju){ echo '执行成功'; } else{ echo '执行失败'; } ?> 本文地址: https://www.linuxprobe.com/php-pdo-linux.html
系统运维
2020-02-05 18:28:00
nignx的并发连接数,一般优化后,峰值能保持在 1~3w 左右。

Nginx 的进程模型
Nginx 服务器,正常运行过程中
1.多进程:一个 Master 进程、多个 Worker 进程
2.Master 进程:管理 Worker 进程
3.对外接口:接收外部的操作(信号)
4.对内转发:根据外部的操作的不同,通过信号管理 Worker
5.监控:监控 worker 进程的运行状态,worker 进程异常终止后,自动重启 worker 进程
6.Worker 进程:所有 Worker 进程都是平等的
7.实际处理:网络请求,由 Worker 进程处理;
8.Worker 进程数量:在 nginx.conf 中配置,一般设置为核心数,充分利用 CPU 资源,同时,避免进程数量过多,避免进程竞争 CPU 资源,增加上下文切换的损耗。
思考:
1.请求是连接到 Nginx,Master 进程负责处理和转发?
2.如何选定哪个 Worker 进程处理请求?请求的处理结果,是否还要经过 Master 进程?
HTTP 连接建立和请求处理过程
1.Nginx 启动时,Master 进程,加载配置文件
2.Master 进程,初始化监听的 socket
3.Master 进程,fork 出多个 Worker 进程
4.Worker 进程,竞争新的连接,获胜方通过三次握手,建立 Socket 连接,并处理请求
Nginx 高性能、高并发
1.Nginx 采用:多进程 + 异步非阻塞方式(IO 多路复用 epoll)
2.请求的完整过程:
3.建立连接
4.读取请求:解析请求
5.处理请求
6.响应请求
7.请求的完整过程,对应到底层,就是:读写 socket 事件
Nginx 的事件处理模型
request:Nginx 中 http 请求。
基本的 HTTP Web Server 工作模式:
1.接收请求:逐行读取请求行和请求头,判断段有请求体后,读取请求体
2.处理请求
3.返回响应:根据处理结果,生成相应的 HTTP 请求(响应行、响应头、响应体)
Nginx 也是这个套路,整体流程一致。
模块化体系结构
nginx的模块根据其功能基本上可以分为以下几种类型:
1.event module: 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括ngx_events_module, ngx_event_core_module和ngx_epoll_module等。nginx具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。
2.phase handler: 此类型的模块也被直接称为handler模块。主要负责处理客户端请求并产生待响应内容,比如ngx_http_static_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。
3.output filter: 也称为filter模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有html页面增加预定义的footbar一类的工作,或者对输出的图片的URL进行替换之类的工作。
4.upstream: upstream模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream模块是一种特殊的handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。
5.load-balancer: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。
常见问题剖析
Nginx vs. Apache
网络 IO 模型:
1.nginx:IO 多路复用,epoll(freebsd 上是 kqueue )
2.高性能
3.高并发
4.占用系统资源少
5.apache:阻塞 + 多进程/多线程
6.更稳定,bug 少
7.模块更丰富
场景:
处理多个请求时,可以采用:IO 多路复用 或者 阻塞 IO +多线程
IO 多路服用:一个 线程,跟踪多个 socket 状态,哪个就绪,就读写哪个;
阻塞 IO + 多线程:每一个请求,新建一个服务线程
思考:IO 多路复用 和 多线程 的适用场景?
IO 多路复用:单个连接的请求处理速度没有优势,适合 IO 密集型 场景,事件驱动
大并发量:只使用一个线程,处理大量的并发请求,降低上下文环境切换损耗,也不需要考虑并发问题,相对可以处理更多的请求;
消耗更少的系统资源(不需要线程调度开销)
适用于长连接的情况(多线程模式长连接容易造成线程过多,造成频繁调度)
阻塞IO + 多线程:实现简单,可以不依赖系统调用,适合 CPU 密集型 场景
每个线程,都需要时间和空间;
线程数量增长时,线程调度开销指数增长
Nginx 最大连接数
基础背景:
1.Nginx 是多进程模型,Worker 进程用于处理请求;
2.单个进程的连接数(文件描述符 fd),有上限(nofile):ulimit -n
3.Nginx 上配置单个 worker 进程的最大连接数:worker_connections 上限为 nofile
4.Nginx 上配置 worker 进程的数量:worker_processes
因此,Nginx 的最大连接数:
1.Nginx 的最大连接数:Worker 进程数量 x 单个 Worker 进程的最大连接数
2.上面是 Nginx 作为通用服务器时,最大的连接数
3.Nginx 作为反向代理服务器时,能够服务的最大连接数:(Worker 进程数量 x 单个 Worker 进程的最大连接数)/ 2。
4.Nginx 反向代理时,会建立 Client 的连接和后端 Web Server 的连接,占用 2 个连接
思考:
每打开一个 socket 占用一个 fd
为什么,一个进程能够打开的 fd 数量有限制?
IO 模型
场景:
处理多个请求时,可以采用:IO 多路复用 或者 阻塞 IO +多线程
IO 多路复用:一个 线程,跟踪多个 socket 状态,哪个就绪,就读写哪个;
阻塞 IO + 多线程:每一个请求,新建一个服务线程
思考:
IO 多路复用 和 多线程 的适用场景?
IO 多路复用:单个连接的请求处理速度没有优势
大并发量:只使用一个线程,处理大量的并发请求,降低上下文环境切换损耗,也不需要考虑并发问题,相对可以处理更多的请求;
消耗更少的系统资源(不需要线程调度开销)
适用于长连接的情况(多线程模式长连接容易造成线程过多,造成频繁调度)
阻塞IO + 多线程:实现简单,可以不依赖系统调用。
每个线程,都需要时间和空间;
线程数量增长时,线程调度开销指数增长
select/poll 和 epoll 比较
详细内容,参考:
select poll epoll三者之间的比较
select/poll 系统调用: // select 系统调用 int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); // poll 系统调用 int poll(struct pollfd fds[], nfds_t nfds, int timeout);
select:
查询 fd_set 中,是否有就绪的 fd,可以设定一个超时时间,当有 fd (File descripter) 就绪或超时返回;
fd_set 是一个位集合,大小是在编译内核时的常量,默认大小为 1024
特点:
连接数限制,fd_set 可表示的 fd 数量太小了;
线性扫描:判断 fd 是否就绪,需要遍历一边 fd_set;
数据复制:用户空间和内核空间,复制连接就绪状态信息
poll:
解决了连接数限制:
poll 中将 select 中的 fd_set 替换成了一个 pollfd 数组
解决 fd 数量过小的问题
数据复制:用户空间和内核空间,复制连接就绪状态信息
epoll:event 事件驱动
epoll:event 事件驱动
事件机制:避免线性扫描
为每个 fd,注册一个监听事件
fd 变更为就绪时,将 fd 添加到就绪链表
fd 数量:无限制(OS 级别的限制,单个进程能打开多少个 fd)
select,poll,epoll:
1.I/O多路复用的机制;
2.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
3.监视多个文件描述符
4.但select,poll,epoll本质上都是同步I/O:
5.用户进程负责读写(从内核空间拷贝到用户空间),读写过程中,用户进程是阻塞的;
6.异步 IO,无需用户进程负责读写,异步IO,会负责从内核空间拷贝到用户空间;
Nginx 的并发处理能力
关于 Nginx 的并发处理能力
并发连接数,一般优化后,峰值能保持在 1~3w 左右。(内存和 CPU 核心数不同,会有进一步优化空间) 本文地址: https://www.linuxprobe.com/nginx.html
系统运维
2020-02-03 14:18:00
最近荣耀魔法本( MagicBook Pro , 锐龙R5 3550H)装了Fedora31系统,虽然没有像Deepin 15.11那样出现亮度不能调节和网卡不能用的问题,但小问题也有不少: 触摸板有时启动后失效,需要重启。sudo modprobe -r i2c_hid && sudo modprobe i2c_hid 触摸板的右键失效。【可设置双指单击显示菜单】 待机后进入桌面有时卡死。【连接鼠标点击或者往上拖动屏幕可以显示登陆界面】 外接显示器刷新频率不能高于30赫兹,否则显示错乱。 外接显示器调整分辨率后,桌面卡住,只能长按电源键关机。 蓝牙3鼠标连接正常,但是蓝牙4的连接不上。 chrome下载完文件后,点击“在文件夹中显示”,文件夹很久才打开。 直接双击下载的rpm软件包,图形安装界面不能自动解决依赖,需要sudo dnf install ./xxx.rpm 有时启动后输入法不启动,需要命令行执行ibus restart
🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿
常用命令 #查看人机交互信息 dmesg dmesg | grep error #重启模块 sudo modprobe -r i2c_hid && sudo modprobe i2c_hid
🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿
官方渠道可以升级内核到5.4.13啦🤪

🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿🌿
安装gnome扩展后,设置主题和优化配置,系统变得很友好。😁
系统运维
2020-01-22 19:02:00
有很多个远程桌面,很多台服务器的大佬看过来,我介绍一款流弊的远程桌面批量管理工具
IIS7远程桌面批量管理工具: http://yczm.iis7.com/?lxmd
 其功能有以下:
 1、批量管理WIN系列服务器,VPS,电脑; 2、批量导入服务器的IP,端口,账号和密码
  3、批量打开N个服务器的远程桌面 ;4、远程桌面后,远程窗口右上角会出现 服务器备注的信息
  5、远程桌面后,不影响任务栏显示。可以及时看其他窗口;6、自定义远程桌面窗口分辨率
  7、定时监测服务器是否正常 ;8、服务器到期提醒 ;9、可选择是否加载本地硬盘、硬盘映射
  10、可选择是否加载服务器的声音,远程声卡读取 ;11、可选择是否禁用本地复制到远程的功能
  12、可选择标签式或窗口式批量远程
系统运维
2020-01-22 16:59:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
一个TCC事务框架需要解决的当然是分布式事务的管理。关于TCC事务机制的介绍,可以参考TCC事务机制简介。
一个TCC事务框架需要解决的当然是分布式事务的管理。关于TCC事务机制的介绍,可以参考TCC事务机制简介。
TCC事务模型虽然说起来简单,然而要基于TCC实现一个通用的分布式事务框架,却比它看上去要复杂的多,不只是简单的调用一下Confirm/Cancel业务就可以了的。
本文将以Spring容器为例,试图分析一下,实现一个通用的TCC分布式事务框架需要注意的一些问题。
一、TCC全局事务必须基于RM本地事务来实现
TCC服务是由Try/Confirm/Cancel业务构成的,其Try/Confirm/Cancel业务在执行时,会访问资源管理器(Resource Manager,下文简称RM)来存取数据。
这些存取操作,必须要参与RM本地事务,以使其更改的数据要么都commit,要么都rollback。
这一点不难理解,考虑一下如下场景:
假设图中的服务B没有基于RM本地事务(以RDBS为例,可通过设置auto-commit为true来模拟),那么一旦[B:Try]操作中途执行失败,TCC事务框架后续决定回滚全局事务时,该[B:Cancel]则需要判断[B:Try]中哪些操作已经写到DB、哪些操作还没有写到DB.
假设[B:Try]业务有5个写库操作,[B:Cancel]业务则需要逐个判断这5个操作是否生效,并将生效的操作执行反向操作。
不幸的是,由于[B:Cancel]业务也有n(0<=n<=5)个反向的写库操作,此时一旦[B:Cancel]也中途出错,则后续的[B:Cancel]执行任务更加繁重。
因为相比第一次[B:Cancel]操作,后续的[B:Cancel]操作还需要判断先前的[B:Cancel]操作的n(0<=n<=5)个写库中哪几个已经执行、哪几个还没有执行.
这就涉及到了幂等性问题,而对幂等性的保障,又很可能还需要涉及额外的写库操作,该写库操作又会因为没有RM本地事务的支持而存在类似问题。。。
可想而知,如果不基于RM本地事务,TCC事务框架是无法有效的管理TCC全局事务的。
反之,基于RM本地事务的TCC事务,这种情况则会很容易处理。
[B:Try]操作中途执行失败,TCC事务框架将其参与RM本地事务直接rollback即可。后续TCC事务框架决定回滚全局事务时,在知道“[B:Try]操作涉及的RM本地事务已经rollback”的情况下,根本无需执行[B:Cancel]操作。
换句话说,基于RM本地事务实现TCC事务框架时,一个TCC型服务的cancel业务要么执行,要么不执行,不需要考虑部分执行的情况。
二、TCC事务框架应该接管Spring容器的TransactionManager
基于RM本地事务的TCC事务框架,可以将各Try/Confirm/Cancel业务看成一个原子服务:一个RM本地事务提交,参与该RM本地事务的所有Try/Confirm/Cancel业务操作都生效;反之,则都不生效。
掌握每个RM本地事务的状态以及它们与Try/Confirm/Cancel业务方法之间的对应关系,以此为基础,TCC事务框架才能有效的构建TCC全局事务。
TCC服务的Try/Confirm/Cancel业务方法在RM上的数据存取操作,其RM本地事务是由Spring容器的PlatformTransactionManager来commit/rollback的,TCC事务框架想要了解RM本地事务的状态,只能通过接管Spring的事务管理器功能。
2.1. 为什么TCC事务框架需要掌握RM本地事务的状态?
首先,根据TCC机制的定义,TCC事务是通过执行Cancel业务来达到回滚效果的。仔细分析一下,这里暗含一个事实:只有生效的Try业务操作才需要执行对应的Cancel业务操作。
换句话说,只有Try业务操作所参与的RM本地事务被commit了,后续TCC全局事务回滚时才需要执行其对应的Cancel业务操作
否则,如果Try业务操作所参与的RM本地事务被rollback了,后续TCC全局事务回滚时就不能执行其Cancel业务,此时若盲目执行Cancel业务反而会导致数据不一致。
其次,Confirm/Cancel业务操作必须保证生效。Confirm/Cancel业务操作也会涉及RM数据存取操作,其参与的RM本地事务也必须被commit。
TCC事务框架需要在确切的知道所有Confirm/Cancel业务操作参与的RM本地事务都被成功commit后,才能将标记该TCC全局事务为完成。
如果TCC事务框架误判了Confirm/Cancel业务参与RM本地事务的状态,就会造成全局事务不一致。
最后,未完成的TCC全局,TCC事务框架必须重新尝试提交/回滚操作。重试时会再次调用各TCC服务的Confirm/Cancel业务操作。
如果某个服务的Confirm/Cancel业务之前已经生效(其参与的RM本地事务已经提交),重试时就不应该再次被调用。否则,其Confirm/Cancel业务被多次调用,就会有“服务幂等性”的问题。
2.2. 拦截TCC服务的Try/Confirm/Cancel业务方法的执行,根据其异常信息可否知道其RM本地事务是否commit/rollback了呢?
基本上很难做到,为什么这么说?
第一,事务是可以在多个(本地/远程)服务之间互相传播其事务上下文的,一个业务方法(Try/Confirm/Cancel)执行完毕并不一定会触发当前事务的commit/rollback操作。
比如,被传播事务上下文的业务方法,在它开始执行时,容器并不会为其创建新的事务,而是它的调用方参与的事务,使得二者操作在同一个事务中;同样,在它执行完毕时,容器也不会提交/回滚它参与的事务的。
因此,这类业务方法上的异常情况并不能反映他们是否生效。不接管Spring的TransactionManager,就无法了解事务于何时被创建,也无法了解它于何时被提交/回滚。
第二、一个业务方法可能会包含多个RM本地事务的情况。
比如:A(REQUIRED)->B(REQUIRES_NEW)->C(REQUIRED),这种情况下,A服务所参与的RM本地事务被提交时,B服务和C服务参与的RM本地事务则可能会被回滚。
第三、并不是抛出了异常的业务方法,其参与的事务就回滚了。
Spring容器的声明式事务定义了两类异常,其事务完成方向都不一样:系统异常(一般为Unchecked异常,默认事务完成方向是rollback)、应用异常(一般为Checked异常,默认事务完成方向是commit)。
二者的事务完成方向又可以通过@Transactional配置显式的指定,如rollbackFor/noRollbackFor等。
第四、Spring容器还支持使用setRollbackOnly的方式显式的控制事务完成方向;
最后,自行拦截业务方法的拦截器和Spring的事务处理的拦截器还会存在执行先后、拦截范围不同等问题。
例如,如果自行拦截器执行在前,就会出现业务方法虽然已经执行完毕但此时其参与的RM本地事务还没有commit/rollback。
TCC事务框架的定位应该是一个TransactionManager,其职责是负责commit/rollback事务。
而一个事务应该commit、还是rollback,则应该是由Spring容器来决定的:
Spring决定提交事务时,会调用TransactionManager来完成commit操作;Spring决定回滚事务时,会调用TransactionManager来完成rollback操作。
接管Spring容器的TransactionManager,TCC事务框架可以明确的得到Spring的事务性指令,并管理Spring容器中各服务的RM本地事务。
否则,如果通过自行拦截的机制,则使得业务系统存在TCC事务处理、RM本地事务处理两套事务处理逻辑,二者互不通信,各行其是。
这种情况下要协调TCC全局事务,基本上可以说是缘木求鱼,本地事务尚且无法管理,更何谈管理分布式事务?
三、TCC事务框架应该具备故障恢复机制
一个TCC事务框架,若是没有故障恢复的保障,是不成其为分布式事务框架的。
分布式事务管理框架的职责,不是做出全局事务提交/回滚的指令,而是管理全局事务提交/回滚的过程。
它需要能够协调多个RM资源、多个节点的分支事务,保证它们按全局事务的完成方向各自完成自己的分支事务。
这一点,是不容易做到的。因为,实际应用中,会有各种故障出现,很多都会造成事务的中断,从而使得统一提交/回滚全局事务的目标不能达到,甚至出现”一部分分支事务已经提交,而另一部分分支事务则已回滚”的情况。
比较常见的故障,比如:业务系统服务器宕机、重启;数据库服务器宕机、重启;网络故障;断电等。这些故障可能单独发生,也可能会同时发生。
作为分布式事务框架,应该具备相应的故障恢复机制,无视这些故障的影响是不负责任的做法。
一个完整的分布式事务框架,应该保障即使在最严苛的条件下也能保证全局事务的一致性,而不是只能在最理想的环境下才能提供这种保障。退一步说,如果能有所谓“理想的环境”,那也无需使用分布式事务了。
TCC事务框架要支持故障恢复,就必须记录相应的事务日志。事务日志是故障恢复的基础和前提,它记录了事务的各项数据。
TCC事务框架做故障恢复时,可以根据事务日志的数据将中断的事务恢复至正确的状态,并在此基础上继续执行先前未完成的提交/回滚操作。关注微信公众号:Java技术栈,在后台回复:架构,可以获取我整理的 N 篇架构教程,都是干货。
四、TCC事务框架应该提供Confirm/Cancel服务的幂等性保障
一般认为,服务的幂等性,是指针对同一个服务的多次(n>1)请求和对它的单次(n=1)请求,二者具有相同的副作用。
在TCC事务模型中,Confirm/Cancel业务可能会被重复调用,其原因很多。
比如,全局事务在提交/回滚时会调用各TCC服务的Confirm/Cancel业务逻辑。执行这些Confirm/Cancel业务时,可能会出现如网络中断的故障而使得全局事务不能完成。
因此,故障恢复机制后续仍然会重新提交/回滚这些未完成的全局事务,这样就会再次调用参与该全局事务的各TCC服务的Confirm/Cancel业务逻辑。
既然Confirm/Cancel业务可能会被多次调用,就需要保障其幂等性。
那么,应该由TCC事务框架来提供幂等性保障?还是应该由业务系统自行来保障幂等性呢?
个人认为,应该是由TCC事务框架来提供幂等性保障。如果仅仅只是极个别服务存在这个问题的话,那么由业务系统来负责也是可以的;
然而,这是一类公共问题,毫无疑问,所有TCC服务的Confirm/Cancel业务存在幂等性问题。TCC服务的公共问题应该由TCC事务框架来解决;
而且,考虑一下由业务系统来负责幂等性需要考虑的问题,就会发现,这无疑增大了业务系统的复杂度。
五、TCC事务框架不能盲目的依赖Cancel业务来回滚事务
前文以及提到过,TCC事务通过Cancel业务来对Try业务进行回撤的机制暗含了一个事实:Try操作已经生效。
也就是说,只有Try操作所参与的RM本地事务已经提交的情况下,才需要执行其Cancel操作进行回撤。没有执行、或者执行了但是其RM本地事务被rollback的Try业务,是一定不能执行其Cancel业务进行回撤的。
因此,TCC事务框架在全局事务回滚时,应该根据TCC服务的Try业务的执行情况选择合适的处理机制。而不能盲目的执行Cancel业务,否则就会导致数据不一致。
一个TCC服务的Try操作是否生效,这是TCC事务框架应该知道的,因为其Try业务所参与的RM事务也是由TCC事务框架所commit/rollbac的(前提是TCC事务框架接管了Spring的事务管理器)。
所以,TCC事务回滚时,TCC事务框架可考虑如下处理策略: 如果TCC事务框架发现某个服务的Try操作的本地事务尚未提交,应该直接将其回滚,而后就不必再执行该服务的cancel业务; 如果TCC事务框架发现某个服务的Try操作的本地事务已经回滚,则不必再执行该服务的cancel业务; 如果TCC事务框架发现某个服务的Try操作尚未被执行过,那么,也不必再执行该服务的cancel业务。
总之,TCC事务框架应该保障: 已生效的Try操作应该被其Cancel操作所回撤; 尚未生效的Try操作,则不应该执行其Cancel操作。这一点,不是幂等性所能解决的问题。如上文所述,幂等性是指服务被执行一次和被执行n(n>0)次所产生的影响相同。但是,未被执行和被执行过,二者效果肯定是不一样的,这不属于幂等性的范畴。
六、Cancel业务与Try业务并行,甚至先于Try操作完成
这应该算TCC事务机制特有的一个不可思议的陷阱。
一般来说,一个特定的TCC服务,其Try操作的执行,是应该在其Confirm/Cancel操作之前的。
Try操作执行完毕之后,Spring容器再根据Try操作的执行情况,指示TCC事务框架提交/回滚全局事务。然后,TCC事务框架再去逐个调用各TCC服务的Confirm/Cancel操作。
然而,超时、网络故障、服务器的重启等故障的存在,使得这个顺序会被打乱。比如:
上图中,假设[B:Try]操作执行过程中,网络闪断,[A:Try]会收到一个RPC远程调用异常。
A不处理该异常,导致全局事务决定回滚,TCC事务框架就会去调用[B:Cancel],而此刻A、B之间网络刚好已经恢复。如果[B:Try]操作耗时较长(网络阻塞/数据库操作阻塞),就会出现[B:Try]和[B:Cancel]二者并行处理的现象,甚至[B:Cancel]先完成的现象。
这种情况下,由于[B:Cancel]执行时,[B:Try]尚未生效(其RM本地事务尚未提交),因此,[B:Cancel]是不能执行的,至少是不能生效(执行了其RM本地事务也要rollback)的。
然而,当[B:Cancel]处理完毕(跳过执行、或者执行后rollback其RM本地事务)后,[B:Try]操作完成又生效了(其RM本地事务成功提交),这就会使得[B:Cancel]虽然提供了,但却没有起到回撤[B:Try]的作用,导致数据的不一致。
所以,TCC框架在这种情况下,需要: 将[B:Try]的本地事务标注为rollbackOnly,阻止其后续生效; 禁止其再次将事务上下文传递给其他远程分支,否则该问题将在其他分支上出现; 相应地,[B:Cancel]也不必执行,至少不能生效。
当然,TCC事务框架也可以简单的选择阻塞[B:Cancel]的处理,待[B:Try]执行完毕后,再根据它的执行情况判断是否需要执行[B:Cancel]。
不过,这种处理方式因为需要等待,所以,处理效率上会有所不及。
同样的情况也会出现在confirm业务上,只不过,发生在Confirm业务上的处理逻辑与发生在Cancel业务上的处理逻辑会不一样。
TCC框架必须保证: Confirm业务在Try业务之后执行,若发现并行,则只能阻塞相应的Confirm业务操作; 在进入Confirm执行阶段之后,也不可以再提交同一全局事务内的新的Try操作的RM本地事务。
七、TCC服务复用性是不是相对较差?
TCC事务机制的定义,决定了一个服务需要提供三个业务实现:Try业务、Confirm业务、Cancel业务。
可能会有人因此认为TCC服务的复用性较差。怎么说呢,要是将 Try/Confirm/Cancel业务逻辑单独拿出来复用,其复用性当然是不好的。
Try/Confirm/Cancel 逻辑作为TCC型服务中的一部分,是不能单独作为一个组件来复用的。Try、Confirm、Cancel业务共同才构成一个组件,如果要复用,应该是复用整个TCC服务组件,而不是单独的Try/Confirm/Cancel业务。
八、TCC服务是否需要对外暴露三个服务接口?
不需要。TCC服务与普通的服务一样,只需要暴露一个接口,也就是它的Try业务。
Confirm/Cancel业务逻辑,只是因为全局事务提交/回滚的需要才提供的,因此Confirm/Cancel业务只需要被TCC事务框架发现即可,不需要被调用它的其他业务服务所感知。
换句话说,业务系统的其他服务在需要调用TCC服务时,根本不需要知道它是否为TCC型服务。
因为,TCC服务能被其他业务服务调用的也仅仅是其Try业务,Confirm/Cancel业务是不能被其他业务服务直接调用的。
九、TCC服务A的Confirm/Cancel业务中能否调用它依赖的TCC服务B的Confirm/Cancel业务?
最好不要这样做。
首先,没有必要。TCC服务A依赖TCC服务B,那么[A:Try]已经将事务上下文传播给[B:Try]了,后续由TCC事务框架来调用各自的Confirm/Cancel业务即可;
其次,Confirm/Cancel业务如果被允许调用其他服务,那么它就有可能再次发起新的TCC全局事务。如此递归下去,将会导致全局事务关系混乱且不可控。
TCC全局事务,应该尽量在Try操作阶段传播事务上下文。Confirm/Cancel操作阶段仅需要完成各自Try业务操作的确认操作/补偿操作即可,不适合再做远程调用,更不能再对外传播事务上下文。
综上所述,本文倾向于认为,实现一个通用的TCC分布式事务管理框架,还是相对比较复杂的。一般业务系统如果需要使用TCC事务机制,并不推荐自行设计实现。
这里,给大家推荐一款开源的TCC分布式事务管理器ByteTCChttps://github.com/liuyangming/ByteTCC
ByteTCC基于Try/Confirm/Cancel机制实现,可与Spring容器无缝集成,兼容Spring的声明式事务管理。提供对dubbo框架、Spring Cloud的开箱即用的支持,可满足多数据源、跨应用、跨服务器等各种分布式事务场景的需求。 本文地址: https://www.linuxprobe.com/distributed-transaction-framework.html
系统运维
2020-01-22 10:12:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
26.6 jenkins邮件设置
26.7 插件email-ext
26.8 pojie jenkins管理员密码



26.6 jenkins邮件设置



上一章节,我们配置完php发布。然后还要配置发邮件,失败了或成功了都是要通知到人的
1.系统管理 -> 系统设置 -> Jenkins Location 先设置系统管理员邮件地址,这里的邮件地址需要和后续要设置的发邮件地址一致。
2.往下拉,找到“邮件通知”那一栏
3.填写SMTP服务器,点右侧的高级,打勾“使用SMTP认证”,填写用户名密码,如果使用的是ssl协议,还需要在这里打勾,smtp端口默认为25
4.可以打勾“通过发送测试邮件测试配置”,然后填写接收邮件的地址,点右侧的Test configuration
5.然后就是到已经构建过的job里去配置接收邮件的地址了。在最下面“构建后操作”那一栏,找到E-mail Notification,Recipients填写收邮件人
6.这个收件人只会在job构建失败时才能收到邮件


实例:













然后我们测试一下,能不能发送邮件。我们认为的给他搞错一下,从而让他发邮件
[root@axinlinux-01 ~]# rm -rf /tmp/jenkins_test/* #我们先把之前的构建的内容全部删除
[root@axinlinux-01 ~]# chattr +i /tmp/jenkins_test/ #然后把这个额目录加个i权限(增加该属性 表示文件不能删除 重命名 设定链接 写入以及新增数据)

我们看控制台输出,也能看到已经发送了。再去163邮箱去查看一下




~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



26.7 插件email-ext



上一章,我们配置了jenkins的内置邮箱。但是他只有在构建失败(发送)的时候,才会发邮件。但是我们想让他在构建成功的时候也发送邮件。就用到了email-ext这个插件
1.插件名字Email Extension Plugin,默认已经安装
2.系统管理->系统设置->Extended E-mail Notification
填写SMTP server,点击Use SMTP Authentication,填写用户名、密码、SMTP port等
3.还需到对应构建任务中去配置一下,下拉到“构建后操作”
4.点击“增加构建后操作步骤”,选择“Editable Email Notification”,其中Project Recipient List为接收邮件的收件人,可以在默认内容后面增加额外的收件人邮箱,用逗号分隔
5.点击右下角的“Advanced settings”,定位到“Triggers”,然后点击下方的“Add Trigger”,可以增加发邮件的条件。
6.参考文章http://www.cnblogs.com/zz0412/p/jenkins_jj_01.html
#如有其它需求,可以参考这篇文章。基本都包括了



实例:


















以上,插件的邮箱配置完成。可以重新构建一下,测试是否发邮件



~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



26.8 pojie jenkins管理员密码



忘记jenkins管理员密码
cd /var/lib/jenkins/users/admin
vim config.xml//定位到那一行
删除改行,改为
#jbcrypt:$2a$10$pre7I4liZFdF6ZE05QntTOoKoKa5pCUumaFO/mMzMoH09bNBzyj6O
重启一下jenkins
新密码为aminglinux.com


实例:
[root@axinlinux-01 ~]# cd /var/lib/jenkins/users/admin_625763853940219016/
[root@axinlinux-01 admin_625763853940219016]# ls
config.xml
[root@axinlinux-01 admin_625763853940219016]# vim config.xml #因为jenkins没有用到数据库,他所有的配置都存放于这样的.xml配置文件里。包括用户密码
#jbcrypt:$2a$10$pre7I4liZFdF6ZE05QntTOoKoKa5pCUumaFO/mMzMoH09bNBzyj6O
#这一串字符串是加密的,所代表的密码就是aminglinux.com
然后重启一下服务就可以浏览器上登录了
系统运维
2020-01-21 09:58:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
它们是包含文件和元数据的档案文件。当安装或卸载 RPM 时,此元数据告诉 RPM 在哪里创建或删除文件。正如你将在上一篇文章中记住的,元数据还包含有关“依赖项”的信息,它可以是“运行时”或“构建时”的依赖信息。
例如,让我们来看看 fpaste。你可以使用 dnf 下载该 RPM。这将下载 Fedora 存储库中可用的 fpaste 最新版本。在 Fedora 30 上,当前版本为 0.3.9.2: $ dnf download fpaste ... fpaste-0.3.9.2-2.fc30.noarch.rpm
由于这是个构建 RPM,因此它仅包含使用 fpaste 所需的文件: $ rpm -qpl ./fpaste-0.3.9.2-2.fc30.noarch.rpm /usr/bin/fpaste /usr/share/doc/fpaste /usr/share/doc/fpaste/README.rst /usr/share/doc/fpaste/TODO /usr/share/licenses/fpaste /usr/share/licenses/fpaste/COPYING /usr/share/man/man1/fpaste.1.gz
源 RPM
在此链条中的下一个环节是源 RPM。Fedora 中的所有软件都必须从其源代码构建。我们不包括预构建的二进制文件。因此,要制作一个 RPM 文件,RPM(工具)需要: 给出必须要安装的文件, 例如,如果要编译出这些文件,则告诉它们如何生成这些文件, 告知必须在何处安装这些文件, 该特定软件需要其他哪些依赖才能正常工作。
源 RPM 拥有所有这些信息。源 RPM 与构建 RPM 相似,但顾名思义,它们不包含已构建的二进制文件,而是包含某个软件的源文件。让我们下载 fpaste 的源 RPM: $ dnf download fpaste --source ... fpaste-0.3.9.2-2.fc30.src.rpm
注意文件的结尾是 src.rpm。所有的 RPM 都是从源 RPM 构建的。你也可以使用 dnf 轻松检查“二进制” RPM 的源 RPM: $ dnf repoquery --qf "%{SOURCERPM}" fpaste fpaste-0.3.9.2-2.fc30.src.rpm
另外,由于这是源 RPM,因此它不包含构建的文件。相反,它包含有关如何从中构建 RPM 的源代码和指令: $ rpm -qpl ./fpaste-0.3.9.2-2.fc30.src.rpm fpaste-0.3.9.2.tar.gz fpaste.spec
这里,第一个文件只是 fpaste 的源代码。第二个是 spec 文件。spec 文件是个配方,可告诉 RPM(工具)如何使用源 RPM 中包含的源代码创建 RPM(档案文件)— 它包含 RPM(工具)构建 RPM(档案文件)所需的所有信息。在 spec 文件中。当我们软件包维护人员添加软件到 Fedora 中时,我们大部分时间都花在编写和完善 spec 文件上。当软件包需要更新时,我们会回过头来调整 spec 文件。你可以在 https://src.fedoraproject.org/browse/projects/ 的源代码存储库中查看 Fedora 中所有软件包的 spec 文件。
请注意,一个源 RPM 可能包含构建多个 RPM 的说明。fpaste 是一款非常简单的软件,一个源 RPM 生成一个“二进制” RPM。而 Python 则更复杂。虽然只有一个源 RPM,但它会生成多个二进制 RPM: $ sudo dnf repoquery --qf "%{SOURCERPM}" python3 python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-devel python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-libs python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-idle python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm $ sudo dnf repoquery --qf "%{SOURCERPM}" python3-tkinter python3-3.7.3-1.fc30.src.rpm python3-3.7.4-1.fc30.src.rpm
用 RPM 行话来讲,“python3” 是“主包”,因此该 spec 文件将称为 python3.spec。所有其他软件包均为“子软件包”。你可以下载 python3 的源 RPM,并查看其中的内容。(提示:补丁也是源代码的一部分): $ dnf download --source python3 python3-3.7.4-1.fc30.src.rpm $ rpm -qpl ./python3-3.7.4-1.fc30.src.rpm 00001-rpath.patch 00102-lib64.patch 00111-no-static-lib.patch 00155-avoid-ctypes-thunks.patch 00170-gc-assertions.patch 00178-dont-duplicate-flags-in-sysconfig.patch 00189-use-rpm-wheels.patch 00205-make-libpl-respect-lib64.patch 00251-change-user-install-location.patch 00274-fix-arch-names.patch 00316-mark-bdist_wininst-unsupported.patch Python-3.7.4.tar.xz check-pyc-timestamps.py idle3.appdata.xml idle3.desktop python3.spec
从源 RPM 构建 RPM
现在我们有了源 RPM,并且其中有什么内容,我们可以从中重建 RPM。但是,在执行此操作之前,我们应该设置系统以构建 RPM。首先,我们安装必需的工具: $ sudo dnf install fedora-packager
这将安装 rpmbuild 工具。rpmbuild 需要一个默认布局,以便它知道源 RPM 中每个必需组件的位置。让我们看看它们是什么: $ rpm -E %{_specdir} /home/asinha/rpmbuild/SPECS $ rpm -E %{_sourcedir} /home/asinha/rpmbuild/SOURCES $ rpm -E %{_builddir} /home/asinha/rpmbuild/BUILD $ rpm -E %{_buildrootdir} /home/asinha/rpmbuild/BUILDROOT $ rpm -E %{_srcrpmdir} /home/asinha/rpmbuild/SRPMS $ rpm -E %{_rpmdir} /home/asinha/rpmbuild/RPMS
我已经在系统上设置了所有这些目录: $ cd $ tree -L 1 rpmbuild/ rpmbuild/ ├── BUILD ├── BUILDROOT ├── RPMS ├── SOURCES ├── SPECS └── SRPMS 6 directories, 0 files
RPM 还提供了一个为你全部设置好的工具: $ rpmdev-setuptree
然后,确保已安装 fpaste 的所有构建依赖项: sudo dnf builddep fpaste-0.3.9.2-3.fc30.src.rpm
对于 fpaste,你只需要 Python,并且它肯定已经安装在你的系统上(dnf 也使用 Python)。还可以给 builddep 命令 一个 spec 文件,而不是源 RPM。在手册页中了解更多信息: $ man dnf.plugin.builddep
现在我们有了所需的一切,从源 RPM 构建一个 RPM 就像这样简单: $ rpmbuild --rebuild fpaste-0.3.9.2-3.fc30.src.rpm .. .. $ tree ~/rpmbuild/RPMS/noarch/ /home/asinha/rpmbuild/RPMS/noarch/ └── fpaste-0.3.9.2-3.fc30.noarch.rpm 0 directories, 1 file
rpmbuild 将安装源 RPM 并从中构建你的 RPM。现在,你可以使用 dnf 安装 RPM 以使用它。当然,如前所述,如果你想在 RPM 中进行任何更改,则必须修改 spec 文件,我们将在下一篇文章中介绍 spec 文件。
总结
总结一下这篇文章有两点: 我们通常安装使用的 RPM 是包含软件的构建版本的 “二进制” RPM 构建 RPM 来自于源 RPM,源 RPM 包括用于生成二进制 RPM 所需的源代码和规范文件。
系统运维
2020-01-20 13:24:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
Linux 中重定向和管道介绍
重定向
系统设定的输入输出 默认输出设备:标准输出,STDOUT,1 默认输入设备:标准输入,STDIN,0 标准错误输出:STDERR,2 标准输入:键盘 标准输出和错误输出:显示器
linux中的I/O重定向 标准输入 (stdin): 代码为0,使用<或<<; 标准输出 (stdout): 代码为1,使用>或>>; 标准错误输出(stderr): 代码为2,使用2>或2>>; ">" ">>" >: 覆盖输出 >> :追加输出 # set -C 禁止对已经存在文件使用覆盖重定向;强制覆盖输出,则使用 >| # set +C 关闭上述功能 "2>" "2>>" 2>: 重定向错误输出 2>>: 追加方式 将正确的与错误的分别存入不同的文件中 # ls / /varr > /tmp/var3. out 2> /tmp/ err . out # ls /varr > /tmp/var4. out 2> /tmp/var4. out /dev/null垃圾桶黑洞装置 &>: 重定向标准输出或错误输出至同一个文件(或者2>&1) "<" 输出重定向(将原来需要由键盘输入的数据,改由文件内容来取代) # tr 'a-z' 'A-Z' < /etc/fstab "<<" Here Document(代表结束的输入字符) # cat << END # cat >> /tmp/myfile.txt << EOF /dev/ null , 软件设备,bit bucket,数据黑洞,将内容输出定向到该设备下无任何返回内容
管道
管道是将前一个 命令 的输出作为后一个 命令 的输入
命令1 | 命令2 | 命令3 | ...... # echo "hello world." | tr 'a-z' 'A-Z' # echo "redhat" | passwd --stdin hive # cut -d: -f1 /etc/passwd | sort | tr 'a-z' 'A-Z' # echo "Hello,World." | tee /tmp/hello.out # tee: 显示内容并将内容保存在文件中 # wc -l /etc/passwd | cut -d ' ' -f1 练习: 1 、统计/usr/bin/目录下的文件个数; 2 、取出当前系统上所有用户的 shell ,要求,每种shell只显示一次,并且按顺序进行显示; 3 、思考:如何显示/var/log目录下每个文件的内容类型? 4 、取出/etc/inittab文件的第 6 行; 5 、取出/etc/passwd文件中倒数第 9 个用户的用户名和shell,显示到屏幕上并将其保存至/tmp/users文件中; 6 、显示/etc目录下所有以pa开头的文件,并统计其个数; 7 、不使用文本编辑器,将alias cls=clear一行内容添加至当前用户的.bashrc文件中; 本文地址: https://www.linuxprobe.com/refre-gd-he.html
系统运维
2020-01-19 10:04:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
一、查看Centos7的版本 # cat /etc/centos-release CentOS Linux release 7.6.1810 (Core)
二、添加对应的ZFS安装包资源 # rpm -ivh http://download.zfsonlinux.org/epel/zfs-release.el7_6.noarch.rpm
三、安装ZFS
ZFS模块可以通过两种方式加载到内核,DKMS和kABI。
它们之间的区别是: 基于DKMS安装ZFS模块,然后由于某种原因更新了操作系统的内核,则必须再次重新编译ZFS内核模块, 否则它将无法工作。 基于kABI 安装 ZFS模块,如果更新操作系统的内核,则不需要重新编译。
在本文中,将基于kABI安装ZFS内核模块。
3.1 在CentOS 7上安装ZFS存储库时,默认情况下会启用基于DKMS的存储库。 因此,必须禁用基于DKMS的存储库并启用基于kABI的存储库。
要禁用基于DKMS的ZFS存储库并启用基于kABI的ZFS存储库,编辑ZFS的yum配置文件 # vim /etc/yum.repos.d/zfs.repo [zfs] name=ZFS on Linux for EL7 - dkms baseurl=http://download.zfsonlinux.org/epel/7.6/$basearch/ # enabled=1 enabled=0 # 关闭DKMS安装 metadata_expire=7d gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux [zfs-kmod] name=ZFS on Linux for EL7 - kmod baseurl=http://download.zfsonlinux.org/epel/7.6/kmod/$basearch/ # enabled=0 enabled=1 # 启用kABI安装 metadata_expire=7d gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-zfsonlinux
3.2 安装zfs # yum install zfs
重启服务器
3.3 检查zfs模块
3.3.1 系统重启后,检查内核中是否加载了zfs # lsmod |grep zfs zfs 3564425 4 zunicode 331170 1 zfs zavl 15236 1 zfs icp 270148 1 zfs zcommon 73440 1 zfs znvpair 89131 2 zfs,zcommon spl 102412 4 icp,zfs,zcommon,znvpair
3.3.2 如果没有看到任何输出,则内核没有加载ZFS模块。 在这种情况下,请运行以下命令以手动加载ZFS内核模块。 # modprobe zfs
现在再次运行 lsmod | grep zfs,你应该看到内核中加载的zfs模块
3.3.3 添加系统重启自动加载zfs模块
在/etc/sysconfig/modules添加文件 # vim zfs #!/bin/sh /sbin/modinfo -F filename zfs > /dev/null 2>&1 if [ $? -eq 0 ]; then /sbin/modprobe zfs fi # chmod 755 zfs
注:
在zfs 0.8.2版本中zfs模块启动时没有加载
This seems to be missing from newer versions of ZFS, from a 0.7.12 system:
[root@photosCent ~ ] # cat /usr/lib/systemd/system/zfs-import-scan.service [Unit] Description=Import ZFS pools by device scanning DefaultDependencies=no Requires=systemd-udev-settle.service After=systemd-udev-settle.service After=cryptsetup.target Before=dracut-mount.service Before=zfs-import.target ConditionPathExists= ! /etc/zfs/zpool.cache [Service] Type=oneshot RemainAfterExit=yes ExecStartPre=-/sbin/modprobe zfs ExecStart=/sbin/zpool import -aN -o cachefile=none [Install] WantedBy=zfs-import.target
On a 0.8.2 system:
[root@wp-km system] # cat /usr/lib/systemd/system/zfs-import-scan.service [Unit] Description=Import ZFS pools by device scanning Documentation=man:zpool(8) DefaultDependencies=no Requires=systemd-udev-settle.service After=systemd-udev-settle.service After=cryptsetup.target Before=zfs-import.target ConditionPathExists= ! /etc/zfs/zpool.cache [Service] Type=oneshot RemainAfterExit=yes ExecStart=/sbin/zpool import -aN -o cachefile=none [Install] WantedBy=zfs-import.target
系统运维
2020-01-17 17:35:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
Matplotlib 是 Python 中最基本的可视化工具,官网里 (( https://matplotlib.org/ ) 有无数好资料,但这不是重点,本文肯定和市面上的所有讲解都不一样。
和 NumPy,SciPy, Pandas 一样,要用Matplotlib,首先引用其库。
先来类比一下人类和 Matplotlib 画图过程。
想想平时我们怎么画图,是不是分三步
找画板
用调色板
画画
Matplotlib 模拟了类似过程,也分三步
FigureCanvas
Renderer
Artist
上面是 Matplotlib 里的三层 API:
FigureCanvas 帮你确定画图的地方
Renderer 帮你把想画的东西展示在屏幕上
Artist 帮你用 Renderer 在 Canvas 上画图
95% 的用户 (我们这些凡人) 只需用 Artist 就能自由的在电脑上画图了。
下面代码就是给 matplotlib 起了个别名 mpl,由于用 matplotlib.plot 比较多,也给它起了个别名 plt。
而 %matplotlib inline 就是在 Jupyter notebook 里面内嵌画图的,
在画图中,个人偏好百度 Echarts 里面的一组颜色,因此将其 hex 颜色代码定义出来留在后面用。其中 红色的 r_hex 和 深青色的 dt_hex 是大爱。

ps:专门建立的Python学习扣QUN:⑦⑧④⑦⑤⑧②①④ 从零基础开始到Python各领域的项目实战教程、开发工具与电子书籍。与你分享企业当下对于python人才需求及学好python的高效技巧,不停更新最新教程!
系统运维
2020-01-15 14:00:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
这里注意获取镜像的时候要获取management版本的,不要获取last版本的,management版本的才带有管理界面。
获取镜像
$ docker pull rabbitmq:management
运行镜像
$ docker run --restart=unless-stopped -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management

$ docker run --restart=unless-stopped -d -p 5672:5672 -p 15672:15672 --name rabbitmq -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:management
访问管理界面
访问管理界面的地址就是 http://[宿主机IP]:15672,没指定用户名密码默认的账户guest/guest
创建指定配置文件RabbitMQ
$ mkdir -p /home/rabbitmq/lib /home/rabbitmq/etc /home/rabbitmq/log
$ docker cp -a rabbitmq:/var/lib/rabbitmq /home/rabbitmq/lib/
$ docker cp -a rabbitmq:/etc/rabbitmq /home/rabbitmq/etc/
$ docker cp -a rabbitmq:/var/log/rabbitmq /home/rabbitmq/log/
$ useradd -s /sbin/nologin rabbitmq
$ cd /home/
$ chown -Rf rabbitmq:rabbitmq rabbitmq/
$ docker kill rabbitmq
$ docker rm -f -v rabbitmq
创建带有目录映射的容器
$ docker run --restart=unless-stopped -d -p 5672:5672 -p 15672:15672 --name rabbitmq -v /home/rabbitmq/etc/rabbitmq:/etc/rabbitmq -v /home/rabbitmq/lib/rabbitmq:/var/lib/rabbitmq -v /home/rabbitmq/log/rabbitmq/:/var/log/rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=123456 rabbitmq:management
性能测试
首先下载官方提供的rabbitmq测试工具perf-test, 下载地址
常用命令
$ rabbitmqctl add_user root root //用户名密码都是root
$ rabbitmqctl list_queues 查看等待队列
$ rabbitmqctl stop_app 停止节点
$ rabbitmqctl reset 重置节点
$ rabbitmqctl start_app 启动节点
$ rabbitmqctl cluster_status 查看集群状态
$ rabbitmqctl change_cluster_node_type disc/ram –更改节点为磁盘或内存节点
$ rabbitmqctl set_policy ha-all "hello" '{"ha-mode":"all"}' 将"hello"的队列设置为同步给其它节点,即开启高可用模式
$ rabbitmqctl forget_cluster_node rabbit @rabbit1 将节点从集群中移除
系统运维
2020-01-15 10:25:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
1. 先要安装samba sudo apt-get update sudo apt-get install samba openssh-server
2. 编辑Samba配置文件 sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak sudo vi /etc/samba/smb.conf
找到[homes]项,此项默认是注释掉的,取消其注释,然后修改其具体内容,修改成如下: #======================= Share Definitions ======================= # Un-comment the following (and tweak the other settings below to suit) # to enable the default home directory shares. This will share each # user's home directory as \\server
系统运维
2020-01-12 16:20:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
众所周知,服务器运营人员的工作内容,主要围绕着公司上下所有服务器、网络等硬件平台的运维工作,对每台服务器的状况,如磁盘、内存、网络、CPU等资源情况都要有明确的了解,还要定期对服务器进行巡检和修复,避免因服务器发生故障而导致公司业务开展受阻。但运维人员的精力是有效的,一旦管理的服务器过多服务器,比如让一个运营人员一下子管理100台、1000台服务器,如果运营管理效率无法提升,就极有可能造成服务器故障等问题,不利于公司业务的进展。那么IT运维人员如何才能高效地管理千台服务器呢?
方法:工具软件
上几张效果图
资源监控
安全防护
日志审查
经验:这款软件的基础功能十分完善,资源监控/告警、安全防护、环境部署、站点管理、远程控制等功能在日常服务器的管理中帮助很大,集群化管理让我们不用再一台台服务器的去运行脚本修复问题,一键式的傻瓜操作也让新加入团队的运维新人能够快速上手。更值得一提的是一键安全巡检和一键修复功能,只需要对每台服务器定期进行检测和修复,就能让隐患在最快的时间内发现并解除,避免因服务器故障而导致业务无法开展,让公司业务开展得更加顺畅。
说了很多,不知有没有看懂,总的来说就是在我们孤单的运维过程中,对服务器与服务器应用中的清晰明了,全在我的掌心掌控之中,逃不出五指山,这就是有效且高效的管理,不能了解何谓心明,不能掌控何谓有效。
我用的服务器管理面板是这款( 软件地址 ),你们呢?各位运维小伙伴如果有更好用的管理工具欢迎分享!
系统运维
2019-12-31 15:33:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
Windows安装rabbitmq
1、安装erlang:
先安装erlang,双击erlang的安装文件即可,然后配置环境变量:
ERLANG_HOME=D:\Program Files\erl9.3
追加到path=%ERLANG_HOME%\bin;
打开cmd命令窗口,进入erlang的bin路径,输入erl命令,如果出现如下提示,则说明erlang安装成功:
C:\Program Files\erl8.3\bin>erl
2、安装RabbitMQ,双击安装文件即可,安装完毕后, 设置环境变量:
RABBITMQ_SERVER=C:\Program Files\RabbitMQ Server\rabbitmq_server-3.5.6
追加到path=%RABBITMQ_SERVER%\sbin;
验证RabbitMQ是否安装成功,在CMD命令窗口输入:
C:\Program Files>rabbitmq-service

安装服务,打开cmd窗体,进入D:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.0\sbin路径,
然后执行 rabbitmq-service install 提示安装成功
3、启动:D:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.4\sbin>rabbitmq-service start
提示启动成功;然后安装web管理插件,执行命令如下:
D:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.0\sbin>rabbitmq-plugins enable rabbitmq_management
出现以上提示,说明安装成功,可以通过访问http://localhost:15672 进行测试,默认的登陆账号 为: guest,密码为:guest。
配置远程访问
// 添加用户
rabbitmqctl add_user admin admin
rabbitmqctl add_user test test
// 添加权限
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
rabbitmqctl set_permissions -p "/" test ".*" ".*" ".*"
// 赋予超级管理员角色
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_user_tags test administrator
如果无法创建用户,则在计算机服务中,更改rabbitmq的登录方式如下,为
此账户:.\Administrator
密码:xxxx
确认密码:xxxx
------------------------------------
CentOS安装RabbitMQ
1、yum安装构建Erlang/OTP所需要的工具
yum install make gcc glibc-devel m4 ncurses-devel autoconf openssl-devel
yum install unixODBC unixODBC-devel
解包和构建Erlang/OTP所需要的工具
2、安装erlang
下载http://erlang.org/download/otp_src_20.0.tar.gz
tar -xvf otp_src_20.0.tar.gz
cd otp_src_20.0
./configure --prefix=/usr/local/erlang --enable-hipe --enable-threads --enable-smp-support --enable-kernel-poll --without-javac
make && make install
ln -s /usr/local/erlang/bin/erl /usr/local/bin/
输入 erl验证是否安装成功。
3、rabbitmq安装步骤
下载https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.7.8/rabbitmq-server-generic-unix-3.7.8.tar.xz
tar -xvf rabbitmq-server-generic-unix-3.7.8.tar -C /opt/
cd /opt
mv rabbitmq_server-3.7.8 rabbitmq_server
cd rabbitmq_server/
sbin/rabbitmq-plugins enable rabbitmq_management
后台启动rabbitmq-server命令./rabbitmq-server -detached
关闭rabbitmq-server命令:./rabbitmqctl stop
最后一定要把rabbitmq-server关闭,等全部配置调整完毕在启动。
4、调整rabbitmq配置信息
在/opt/rabbitmq_server/etc/rabbitmq下面创建文件rabbitmq.config和rabbitmq-env.conf,文件内容如下:
[root @node1 rabbitmq]# pwd
/opt/rabbitmq_server/etc/rabbitmq
[root @node1 rabbitmq]# more rabbitmq.config
[
{rabbit,
[{loopback_users, []}]
}
]
more rabbitmq-env.conf
LOG_BASE=/data/rabbitmq
MNESIA_BASE=/data/rabbitmq/mnesia
5、防火墙设置
-A INPUT -p tcp -m tcp --dport 5672 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 15672 -j ACCEPT
service iptables restart
service iptables save
------------------------------------
CentOS安装RabbitMQ集群环境
加入安装了两台服务192.168.1.101,192.168.1.102.
1、在192.168.1.101配置
/etc/hosts设置
vi /etc/hosts
192.168.1.101 rabbitmq1
192.168.1.102 rabbitmq2
rabbitmq-env 设置
在/opt/rabbitmq_server/sbin/rabbitmq-env文件的最前面添加:
NODENAME=rabbit@rabbitmq1
启动rabbitmq-server
后台启动rabbitmq-server命令./rabbitmq-server -detached
./rabbitmqctl add_user admin 123456
./rabbitmqctl set_user_tags admin administrator
2、在192.168.1.102配置
修改.erlang.cookie
chmod 600 ~/.erlang.cookie
修改~/.erlang.cookie的内容,和192.168.1.101的~/.erlang.cookie内容保持一致。
/etc/hosts设置
vi /etc/hosts
192.168.1.101 rabbitmq1
192.168.1.102 rabbitmq2
rabbitmq-env 设置
在/opt/rabbitmq_server/sbin/rabbitmq-env文件的最前面添加:
NODENAME=rabbit@rabbitmq2
启动rabbitmq-server
后台启动rabbitmq-server命令./rabbitmq-server -detached
./rabbitmqctl add_user admin 123456
./rabbitmqctl set_user_tags admin administrator
3、将rabbit@rabbitmq2加入集群
停止应用:./rabbitmqctl stop_app(rabbitmqctl stop 是停止服务)
清除所有队列:./rabbitmqctl reset
将rabbit@rabbitmq2加入集群:./rabbitmqctl join_cluster rabbit@rabbitmq2
重启应用:./rabbitmqctl start_app
查看集群状态:./rabbitmqctl cluster_status
4、登录Rabbitmq
http://192.168.1.101:15672/
用户名和密码:guest/guest
用户名和密码:admin/123456
http://192.168.1.102:15672/
用户名和密码:guest/guest
用户名和密码:admin/123456

系统运维
2019-12-29 22:08:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
20191219 用 phpvirtualbox 管理虚拟机的时候,闲来整理一下多余的虚拟机 关闭并删除多余的、不再使用的服务器 对正在使用的虚拟机加上注解( Description) 升级一下还在使用 ubuntu 1804.1 的发行版(release)到ubuntu 1804.3 克隆一个种子以备下次使用
贪心缘故,以上几个操作我同时进行,结果悲剧了! phpvirtualbox 网站弹出来一个异常警告
详细信息如下
Exception Object ( [message:protected] => Could not connect to host ( http://127.0.0.1:18083/ ) [string:Exception:private] => [code:protected] => 64 [file:protected] => /var/www/html/phpvirtualbox/endpoints/api.php [line:protected] => 135 [trace:Exception:private] => Array ( ) [previous:Exception:private] =>
)
克隆的没有完成,删除的不知道是否真的就删除了!
重复几次打开网站,换浏览器打开网站,都是一样! 于是继上次 20190216 十个月之后,又一次开始折腾 phpvirtualbox 之旅
1、赶紧打开自己之前的一次折腾记录
https://blog.csdn.net/u010953609/article/details/87518537
2、不可惊慌! 上一次的折腾的时候刚开始使用,没有什么虚拟机在使用中,大不了全部重启、关机、重装,后来也确实重装了 virtualbox 和 phpvirtualbox 才恢复 本次虽然有了经验,但是若干虚拟机已经在正常使用中,不可能全部重装!不可贸然关机或者重启!不可贸然重启 vbox 服务 (vboxweb-service) 冷静分析,其实 phpvirtualbox 只是一个远程管理虚拟机的网站,并不会实际影响虚拟机的运行,所以,只要想法恢复网站,就不会造成什么后果!
3、尝试 VBoxManage 命令行 列举所有虚拟机 VBoxManage list vms 那些 vms 列表正常,本次不再粘贴 启动被关机的虚拟机 假设被关机的虚拟机名字是: vmsClosed
VBoxManage startvm vmsClosed 失败了!警告信息如下 Waiting for VM "vmsClosed" to power on... VBoxManage: error: The virtual machine 'vmsClosed' has terminated unexpectedly during startup with exit code 1 (0x1) VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component MachineWrap, interface IMachine 启动克隆出来的虚拟机 假设克隆出来的虚拟机名字是: vmsClone VBoxManage startvm vmsClone 失败了!警告信息如下
VBoxManage: error: The machine 'vmsClone' is already locked by a session (or being locked or unlocked) VBoxManage: error: Details: code VBOX_E_INVALID_OBJECT_STATE (0x80bb0007), component MachineWrap, interface IMachine, callee nsISupports VBoxManage: error: Context: "LaunchVMProcess(a->session, sessionType.raw(), Bstr(strEnv).raw(), progress.asOutParam())" at line 600 of file VBoxManageMisc.cpp 关机 VBoxManage controlvm XXXXXX 找一个不重要的虚拟机,关机试试!也失败了! 信息忘记保留了
4、 尝试重启 网站 重启 apache sudo systemctl restart apache2 再次打开网站,依然是以上的异常 本地打开网站 curl 127.0.0.1/phpvirtualbox/ 出现以下错误 Could not connect to host ( http://127.0.0.1:18083/ ) *** 原来是 18083 端口问题 查看 lsof -i:18083 netstat -antp |grep 18083 忘记了记录以上信息 重启 vboxweb-service sudo systemctl restart vboxweb-service 再次打开网站,依然是以上的异常 重启 vboxdrv sudo systemctl restart vboxdrv 再次打开网站,恢复正常了!
*** 以上启动次序,是按照我个人理解的影响从小到大,一步一步的进行的! *** 如果还不行,可能就需要重启 virtualbox 整个服务了,那样子一定会影响所有的虚拟机!
5 、总结
到底是什么原因,造成了 18083 端口阻塞?不得而知! php 网站异步处理是个问题,所以,不要想我一样同时进行几个操作(克隆、删除、注解...),等一个操作完成之后,再进行下一个 最终恢复是重启了 vboxdrv 驱动,没有影响到整个虚拟机环境!
*** 后记:20191221 又一次出现以上异常! 这次是因为给某个虚拟机加注解( Description),其中使用了问号(?)等特殊字符
** 注解不要带有特殊字符!**
我分析:既然造成了阻塞,多等一会儿应该可以恢复!所以,特意等待了师傅哦分钟,果然,
phpvirtualbox 网站自己恢复了! *
系统运维
2019-12-22 10:38:00
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
本篇讲 “故障自动维修流程”里 “环境初始化”这个环节。

初始化的问题—环境不一致
可能大家会觉得,环境初始化有什么好说的,不就是跑一堆设置系统参数的脚本么? 事实上,设置环境很容易,但是要保证环境设置正确会遇到很多问题。

环境不一致影响业务的case
先来看我们对业务sre 的访谈,因“环境设置不正确”导致业务受损的case有很多,如下所示, 因超线程未开启,导致服务在流量高峰时性能不足,产生请求拒绝 因QoS未设置,导致跨机房查询数据时,响应延迟飙高,大面积拖慢了了用户访问速度 因未设置ssd磁盘内核参数,导致磁盘处于低性能状态,影响业务读取速度 因网卡多队列未正确设置,导致单个cpu被打满,产生拒绝请求 因基础agent版本不一致,影响变更、数据配送任务,产生了脏数据 因core pattern 未正确设置,业务程序出core打满磁盘,拉长了止损时间 因环境缺失/版本不符导致业务程序依赖异常,产生请求拒绝, 如mysql/hadoop client 缺失, python/perl 版本过老 因内核网络、内存参数未正确设置,导致业务出现性能颠簸问题,产生间歇性请求拒绝,排查成本高
上述case,都是因部分机器环境未正确设置导致的,也就是机器环境存在“不一致”的情况。

环境不一致的原因
为什么会不一致?我们分析了各个业务初始化脚本集合,得到的结论是:“硬件/系统多样、业务需求差异化、初始化流程不统一不规范” 这3个因素综合作用导致了“环境不一致”。

1、硬件/系统多样
机器硬件不一样,设置某个功能的命令是不一样的。
比如开启超线程,戴尔机器和惠普机器不一样,这导致同一个功能,业务sre会写出多个脚本, 例如 cpu_ht_dell.sh, cpu_ht_hp.sh;
同样道理,系统发行版不一样,开机启动、参数文件位置、内核参数的设置方式有可能不一样,对于同一个需求,就会存在多个脚本,长期下来,几乎无法维护。

2、业务需求差异化
不同的业务类型,通常会根据自身需要,调优设置各种参数。

如接入层机器,需设置tcp内核参数,包括缓冲区大小、拥塞窗口大小;

存储层机器,需设置内核换页、磁盘调度策略、shm参数;

计算密集型业务机器,需开启超线程,QoS优先级设置为高;

这些差异化的存在,使得各个业务sre小组都维护着一套自己的初始化脚本集合,很难共用、复用。

3、初始化流程不统一、不规范
业务sre执行初始化时,没有统一规范,初始化的执行方式多样(pssh/ansible/部署系统),而且没有强制性检查。
由于环境初始化失败,不会影响服务的初期运行,只有在流量大的时候才会出现问题,如果执行初始化的sre经验不足或者不够细心,这些“环境初始化失败”的机器,也会投入使用,造成隐患。


怎么解决不一致
在自己动手解决问题之前,我们粗略地调研了业界的环境管理方案:puppet,存在以下问题, puppet管理的资源中,有file, service, yum 等, 但不包括硬件,如BIOS、超线程、网卡多队列; 另外,关于service 这个资源,涉及到版本问题,需要和部署系统联动,puppet似乎没有提供相应的功能 puppet has its own configuration language, puppet 通过自成体系的配置语言来声明资源,控制任务的执行,这对我们来说是一个很大的学习成本,我们需要的是:低成本地使用已经存在的上百个脚本,而不是用别的语言重写它们。 puppet 是 C/S架构,意味着需要部署它自己的agent,在几万台机器的集群上部署一个新agent,风险性很高,需要经历漫长的审核、测试、运行验证过程

解决思路
所以,我们决定还是结合自身问题来解决,分以下几步走,

1、引入apply-check机制,规范初始化的执行和检查,保证交付质量
apply-check强制要求每一个初始化的执行和检查成对出现;
apply 即对文件或内存写入,check即对文件或内存读出并做对比判断,例如,
Item 超线程
Apply ipmitool raw 0x3e 0x20 0x00 0x00 0x00
Check cat /proc/cpuinfo | grep -o ht | uniq
这样,可以明确知道初始化是否成功。

2、根据机器品牌,部署硬件/BIOS/系统发行版相关命令工具

如图所示,超线程的初始化apply、check脚本分别软链到了相应厂商的实现脚本,这样就可以在执行时,无需关注机器厂商、系统的差异,直接调用,消除适配问题。

3、明确业务类型和初始化集合的关系
我们引入 Pool 的概念,每个Pool对应一个环境初始化集合,如下表所示,
Pool Web 计算 存储 通用
超线程 on on off off
网卡多队列 on on on on
QoS 高 高 高 中
Tcp参数集 on on off off
Disk io参数集 off off on off
… … … … …

这样,给定一台机器,只要知道归属于哪个集群,就能通过调用相应的check来判断这机器的环境是否正确,是否一致。


解决环境不一致问题

做了上述优化之后,我们在机器重装系统流程中增加了apply-check的逻辑,可以保证环境一定是正确的,因为只有在所有初始化的check通过之后,系统才会交付。

对于存量机器,我们把环境不一致当作一种故障来处理,复用了硬件故障维修的流程框架。
环境不一致修复的工作流程, 环境检测: 每5分钟发起一次环境检测,即调用初始化的check脚本,获取当前时刻整个集群的环境不一致的机器列表,推送到工作流子系统 安全策略: 遍历机器列表,依次执行安全策略,过滤不符合要求的机器,得到一个可安全执行初始化脚本的机器列表 执行初始化: 根据机器所属pool,执行相应初始化apply脚本 检查初始化:根据机器所属pool,执行相应初始化check脚本,如果初始化正确,流程结束 如果初始化不正确,那么认为此机器存在硬件或系统级别的故障,生成repair_host流程,本流程结束。
(故障修复流程框架的详情请参阅 大规模机器集群-故障自动处理(一) )

通过这两点,逐步消除存量机器的环境不一致,同时保证新交付的机器一定是正确的。


在解决基础环境一致性问题之后,结合硬件故障自动维修,我们得到了一个能力,
输入: 任何一台机器,不管是否有故障,环境是否正确
输出: 无硬件故障,符合业务环境需求的可用机器

这为后续做机房建设,灾备重建打下了基础,下一篇会讲这部分内容。

排列文字,重组感受。
我是曲行人,日常写码,闲时写点儿文字,
如果你觉得有点意思,或者有点用,可以关注我,
我将在大脑里的思维原子做布朗运动时,输出文字。
公众号: qxren7
二维码:
系统运维
2019-12-22 08:34:00