Linux系统时间同步的两种方法

偶然下午折腾v2ray,发现因为系统时间和本地时间不一致搞得我被reject了
正好提醒我写一个关于 调教(#滑稽) Linux系统时钟 的帖子

Linux的两种时间

Linux时钟分为 系统时钟(System Clock)和 硬件时钟(Real Time Clock,简称RTC)
Linux下,默认情况下,系统时间和硬件时间并不会自动同步。系统运行过程中,系统时间和硬件时间异步计时,互不干扰。
系统时钟是当前Linux Kernel的时钟,而硬件时钟是主板上由电池供电的时钟,硬件时钟可在BIOS中设置。当Linux启动时,硬件时钟会去读取系统时钟的设置,并独立于硬件时间进行计时。
Linux中的所有命令(包括函数)均采用系统时钟。
Linux中,时钟相关的命令主要有 date 和 hwclock

date

date [指令] [参数]
设定时间

#设定日期20170730 时刻归零00:00:00  
date --s "20170730"  
#设定时刻17:32:59 日期保持不变  
date --s "17:32:59"  
#以下六种均可同时设定日期和时刻  
date --s "20170730 17:32:59"  
date --s "17:32:59 20170730"  
date --s "2017/07/30 17:32:59"  
date --s "17:32:59 2017/07/30"  
date --s "17:32:59 07/30/2017"  
date --s "07/30/2017 17:32:59"

hwclock

#将系统时间写入硬件时间  
hwclock --systohc  
#将硬件时间写入系统时间  
hwclock --hctosys  
#将当前时间写入BIOS 避免重启后失效  
hwclock -w

Linux的时区

时区是什么当然不用解释了
这一块讲解怎么设定时区

查看当前时区

date -R

手动设定时区

tzselect

跟着提示一步步完成选择后,复制相应的时区文件,替换系统时区文件;或者创建ln -s链接

例如修改时区为中国上海

cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

时间同步的两种方式

手动设定或多或少会有偏差(将来出了偏差…),这时就可从时间服务器更新时间以获得更高精确度

ntpdate更新时间

如果你的linux提示ntpdate:command not found

debian: apt-get install ntpdate  
centos: yum install ntpdate

安装完成后可直接执行命令

ntpdate [ntp时间服务器地址]  
[root@nanqinlang ~] ntpdate time.nist.gov  
30 Jul 18:01:08 ntpdate[11063]: adjust time server 216.229.0.179 63592.064680 sec

出现上面的内容即同步成功,然后在crontab里面加上以下:
*/360 * * * * ntpdate time.nist.gov #每隔六小时同步一次
推荐以下时间服务器:

time.nist.gov  
time.nuri.net  
0.asia.pool.ntp.org  
1.asia.pool.ntp.org  
2.asia.pool.ntp.org  
3.asia.pool.ntp.org

ntp自建时间服务器

上面我提到使用ntp时间服务器来同步时间,这些时间服务器都是较权威的
而当我们自己搭建时间服务器时,就不用crontab定时去跑了

[root@nanqinlang ~] apt-get install ntp -y  
[root@nanqinlang ~] yum install ntp -y  
[root@nanqinlang ~] cat /etc/ntp.conf |awk '{if($0 !~ /^$/ && $0 !~ /^#/) {print $0}}' #修改配置文件  
restrict default kod nomodify notrap nopeer noquery #拒绝IPV4用户  
restrict -6 default kod nomodify notrap nopeer noquery #拒绝IPV6用户  
restrict 192.168.100.0 mask 255.255.255.0 nomodify #本地网段授权访问  
restrict time.nist.gov #授权访问本地NTP  
restrict 0.asia.pool.ntp.org  
restrict 1.asia.pool.ntp.org  
restrict 2.asia.pool.ntp.org  
restrict 127.0.0.1  
restrict -6 ::1  
server time.nist.gov prefer #设定时间服务器,prefer表示优先  
server 0.asia.pool.ntp.org  
server 1.asia.pool.ntp.org  
server 2.asia.pool.ntp.org  
driftfile /var/lib/ntp/drift  
keys /etc/ntp/keys  
[root@nanqinlang ~] service ntp restart #重启服务  
[root@nanqinlang ~] netstat -tlunp | grep ntp #查看进程,若看到123则启动成功  
udp 0 0 136.243.26.229:123 0.0.0.0:*  
11139/ntpd  
udp6 0 0 ::1:123 :::*  
11139/ntpd  
[root@nanqinlang ~] ntpq -pn #查看同步的服务器IP  
remote refid st t when poll reach delay offset jitter  
+61.216.153.104 118.163.81.62 3 u 5 64 1 62.575 10.842 1.198  
+212.47.249.141 5.103.128.88 3 u 4 64 1 217.645 -12.155 0.224  
-51.15.41.135 5.103.128.88 3 u 3 64 1 230.814 -26.141 0.702  
*108.59.2.24 130.133.1.10 2 u 2 64 1 235.620 -8.041 0.207  
[root@nanqinlang ~] ntpstat #同步的结果  
synchronised to local net at stratum 11  
time correct to within 12 ms  
polling server every 512 s

其中:

  • remote: NTP主机IP。最左边的符号,若”+”则表示是正在作用的上游NTP,若”*”则表示也有连上,只是作为次要NTP
  • refid: 参考的上游NTP地址
  • st: stratum阶层
  • when: 几秒前曾进行同步
  • poll: 多少秒后进行下次同步
  • reach: 已向上游NTP请求同步的次数
  • delay: 网络传输过程中钟延迟的时间
  • offset: 时间补偿结果
  • jitter: 系统时间与硬件时间的差异时间

ubuntu16.04更新web服务器apache为Nginx php-fpm

网站一直跑的是apache,觉得卡卡的。换nginx玩玩喽。nginx还是很强大的,感觉速度有些提升,奥力给!!!

已经安装了Apache2的话,要先删除再安装nginx!

删除Apache2

service apache2 stop
update-rc.d -f apache2 remove
apt-get remove apache2

安装nginx

apt-get -y install nginx

service nginx start

输入ip或域名就能看到 Welcome to Nginx!

安装 PHP 7

我们可以通过使nginx的PHP工作PHP-FPM(PHP-FPM(FastCGI进程管理器)是为任何规模的网站,尤其是繁忙的网站有用的一些附加功能的替代PHP的FastCGI实现),我们安装如下:

apt-get -y install php7.0-fpm

配置 nginx

虚拟主机服务器{}容器定义。默认的虚拟主机是在文件中定义的/etc/nginx/sites-available/default

将index index.html index.htm index.nginx-debian.html;

改成index index.html index.htm index.php;

server_name _;这里改成你的主机名,一般是域名。例如:server_name zerlong.com;

找到下边这一段,把注释去掉

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        location ~ .php$ {
                include snippets/fastcgi-php.conf;

                # With php7.0-cgi alone:
                #fastcgi_pass 127.0.0.1:9000;
                # With php7.0-fpm:
                fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        }

注意上边fastcgi_pass只能用一个,不然会出错类似”fastcgi_pass”

directive is duplicate in /etc/nginx/sites-enabled/default

保存完重新加载nginx:

service nginx reload

编辑/etc/php/7.0/fpm/php.ini 设置 cgi.fix_pathinfo=0

service php7.0-fpm reload

建立个探针文件访问一下能出信息了,

未分类

MySQL 获得 PHP 7支持

使用下面的命令安装:

apt-get -y install php7.0-mysql php7.0-curl php7.0-gd php7.0-intl php-pear php-imagick php7.0-imap php7.0-mcrypt php-memcache  php7.0-pspell php7.0-recode php7.0-sqlite3 php7.0-tidy php7.0-xmlrpc php7.0-xsl php7.0-mbstring php-gettext

APCu是随PHP7 PHP Opcache模块的扩展,它增加了一些兼容性功能的支持APC缓存(例如WordPress的插件缓存)软件。

APCu可以安装如下:

apt-get -y install php-apcu

重新加载 PHP-FPM:

service php7.0-fpm reload

好了,跑起来吧网站!

附送nginx+https SSL配置

同时启用HTTP和HTTPS

server {
    listen              80;
    listen              443 ssl;
    server_name         zerlong.com;    #最好写绝对路径,你懂的
    ssl_certificate     zerlong.com.crt;
    ssl_certificate_key zerlong.com.key;
    ...

自动跳转https

server {
        listen   80;
        server_name zerlong.com;
        rewrite ^ https://$http_host$request_uri? permanent;    # force redirect http to https
        #return 301 https://$http_host$request_uri;
    }

server {
        listen 443 ssl;

        ssl_certificate /etc/nginx/ssl/nginx.crt;
        ssl_certificate_key /etc/nginx/ssl/nginx.key;

        server_name zerlong.com;
        #禁止在header中出现服务器版本,防止黑客利用版本漏洞攻击
        server_tokens off;
        #如果是全站 HTTPS 并且不考虑 HTTP 的话,可以加入 HSTS 告诉你的浏览器本网站全站加密,并且强制用HTTPS 访问
        #add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
        # ......
        fastcgi_param   HTTPS              on;
        fastcgi_param   HTTP_SCHEME      https;

    }

wordpress的nginx伪静态规则

在站点配置文件的server { } 大括号里面添加下面的代码,然后重启nginx

location / {
if (-f $request_filename/index.html){
                rewrite (.*) $1/index.html break;
        }
if (-f $request_filename/index.php){
                rewrite (.*) $1/index.php;
        }
if (!-f $request_filename){
                rewrite (.*) /index.php;
        }
}

Linux crontab定时任务不执行时的分析方法

在使用配置crontab过程中一直不能执行配置的任务,折腾了半夜没有解决,随找到如下文章,查问题的思路很清晰,帮我找到了问题,直接转:

crond是Linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。

使用权限:

root用户和crontab文件的所有者

语法:

crontab [-e [UserName]|-l [UserName]|-r [UserName]|-v [UserName]|File ]

说明:

crontab 是用来让使用者在固定时间或固定间隔执行程序之用,换句话说,也就是类似使用者的时程表。-u user 是指设定指定 user 的时程表,这个前提是你必须要有其权限(比如说是 root)才能够指定他人的时程表。如果不使用 -u user 的话,就是表示设定自己的时程表。

参数:

  • -e [UserName]: 执行文字编辑器来设定时程表,内定的文字编辑器是 VI,如果你想用别的文字编辑器,则请先设定 VISUAL 环境变数来指定使用那个文字编辑器(比如说 setenv VISUAL joe)
  • -r [UserName]: 删除目前的时程表
  • -l [UserName]: 列出目前的时程表
  • -v [UserName]:列出用户cron作业的状态

自拉起脚本很简单,随便写几行就搞定了:

Shell
#!/bin/bash
processcount=$(pgrep my_app|wc -l)
cd $(cd $(dirname $0) && pwd)
if [[ 0 -eq $processcount ]]
then
echo “[ $(date) ] : my_app is down, start it!” | tee -ai ./checkprocess.log
bash ./start.sh #这里是项目的重启脚本
else
echo my_app is OK!
fi
然后丢到 crontab,1分钟执行一次:
Shell

* * * * * bash /data/app_server/checkprocess.sh >/dev/null 2>&1

本以为万事大吉了,结果还是坑了,进程再一次挂了,尼玛什么鬼?

一、检查日志

根据经验,先看一下crontab的日志:

tail /var/log/messages

没发现相关日志,看来不是打印到了这,于是查看了下crontab的默认日志位置:

tail /var/log/cron

Mar 25 21:40:01 li733-135 CROND[1959]: (root) CMD (sh /data/app_server/checkprocess.sh >/dev/null 2>&1)
Mar 25 21:40:01 li733-135 CROND[1960]: (root) CMD (/usr/lib64/sa/sa1 1 1)
Mar 25 21:40:01 li733-135 CROND[1961]: (root) CMD (/usr/sbin/ntpdate pool.ntp.org > /dev/null 2>&1)
Mar 25 21:41:01 li733-135 CROND[2066]: (root) CMD (sh /data/app_server/checkprocess.sh >/dev/null 2>&1)

很明显,任务计划确实在正常执行着,看来问题在脚本上了。

二、检查脚本

1、直接执行

检查脚本第一步,直接按照crontab里面的命令行,执行脚本:

sh /data/app_server/checkprocess.sh
[ Fri Mar 25 21:25:01 CST 2016 ] : my_app is down, start it!

sh /data/app_server/checkprocess.sh
my_app is OK!

结果进程正常拉起了!
直接执行成功,而放到crontab就失败,经验告诉我肯定的脚本环境变量有问题了!

2、环境变量

于是在脚本里面载入环境变量:

#!/bin/bash
#先载入环境变量
source /etc/profile
#其他代码不变

然后手工把进程杀死,等待自拉起,结果… 还是不行!

3、系统邮件

经验告诉我,crontab执行失败,如果没有屏蔽错误的话,会产生一个系统邮件,
位置在 /var/spool/mail/root
所以,我把crontab里面的 2>&1 这个屏蔽错误先取消掉,等待几分钟查看邮件。

cat /var/spool/mail/root 发现有如下报错:

From [email protected] Fri Mar 25 21:30:02 2016
Return-Path: <root@app_server.localdomain>
X-Original-To: root
Delivered-To: root@app_server.localdomain
Received: by app_server.localdomain (Postfix, from userid 0)
id 78DB5403E2; Fri, 25 Mar 2016 21:19:02 +0800 (CST)
From: root@app_server.localdomain (Cron Daemon)
To: root@app_server.localdomain
Subject: Cron <root@app_server> bash /data/app_server/checkprocess.sh >/dev/null
Content-Type: text/plain; charset=UTF-8
Auto-Submitted: auto-generated
X-Cron-Env: <LANG=en_US.UTF-8>
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <HOME=/root>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>
Message-Id: <20160325131902.78DB5403E2@app_server.localdomain>
Date: Fri, 25 Mar 2016 21:19:02 +0800 (CST)

start.sh: line 4: /sbin/sudo: No such file or directory #sudo命令找不到!我次奥·~

居然是脚本里面的sudo执行失败了,找不到这个文件。看来单纯的载入 profile 不一定靠谱啊!

4、修复脚本

知道问题所在,解决就简单了,粗暴点,直接写入sudo的绝对路径

/usr/bin/sudo

继续测试自拉起,结果… 还是不行!R了G了!!

三、最终解决

继续查看了下系统邮件,发现如下信息:

Subject: Cron <root@free-node-us> source /etc/profile;bash /data/app_server/checkprocess.sh >/dev/null
Content-Type: text/plain; charset=UTF-8
Auto-Submitted: auto-generated
X-Cron-Env: <LANG=en_US.UTF-8>
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <HOME=/root>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=root>
X-Cron-Env: <USER=root>
Message-Id: <20160325132403.0E8E1403E2@app_server.localdomain>
Date: Fri, 25 Mar 2016 21:24:03 +0800 (CST)

sudo: sorry, you must have a tty to run sudo #原来是这个问题!

很明显,提示了sudo必须需要tty才能执行,解决很简单,取消这个限制即可!
编辑 /etc/sudoers ,找到 Defaults requiretty, 然后注释掉这行:

vim /etc/sudoers

#Defaults requiretty

最后使用 :x! 或 :wq! 强制保存即可。
结果观察还是报了相同的错误!原来改完这个sudo并不会影响已经运行的crontab,所以需要重启crontab服务刷新下设置:

service crond restart

这下终于可以了!

四、分析总结

Linux系统里面计划任务,crontab 没有如期执行这是运维工作中比较常见的一种故障了,根据经验,大家可以从如下角度分析解决:

1、检查crontab服务是否正常

这个一般通过查看日志来检查,也就是前文提到的 /var/log/cron 或 /var/log/messages,如果里面没有发现执行记录,那么可以重启下这个服务:service crond restart

2、检查脚本的执行权限

一般来说,在crontab中建议使用 sh 或 bash 来执行shell脚本,避免因脚本文件的执行权限丢失导致任务失败。当然,最直接检查就是人工直接复制crontab -l 里面的命令行测试结果。

3、检查脚本需要用到的变量

和上文一样,通常来说从crontab里面执行的脚本和人工执行的环境变量是不一样的,所以对于一些系统变量,建议写绝对路径,或使用witch动态获取,比如 sudo_bin=$(which sudo) 就能拿到 sudo在当前系统的绝对路径了。

4、放大招:查看日志

其实,最直接最有效的就是查看执行日志了,结合crontab执行记录,以及crontab执行出错后的系统邮件,一般都能彻底找到失败的原因了!当然,要记住在crontab中如果屏蔽了错误信息,就不会发邮件了。
这又让我想起了如果crontab未屏蔽日志,可能会导致硬盘 inode 爆满

Linux配置定时任务的两种方法crontab -e与/etc/crontab及其区别

Linux配置定时任务,大家都知道使用crontab这个系统功能,但有时候我们需要区分用户执行,下面就直接说一下2种方法的区别:

方法1:

使用命令 crontab -e 然后直接编辑定时脚本。

这样执行以后,属于用户自定义的,会被写到 /var/spool/cron 目录下,生成一个和用户名一致的文件,文件内容就是我们编辑的定时脚本。

如:

[root@localhost cron.d]# cd /var/spool/cron  
[root@localhost cron]# ll  
总用量 4  
-rw-------. 1 root root 52 12月  9 10:58 root  
[root@localhost cron]# pwd  
/var/spool/cron  
[root@localhost cron]# cat root   
30 03 * * * /root/automysqlbackup.sh  

方法2:

使用命令 vi /etc/crontab 编辑定时脚本。

如:


[root@localhost ~]# cat /etc/crontab SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=root HOME=/ # run-parts 30 * * * * root /usr/sbin/ntpdate 210.72.145.44 #30 8 * * * root /usr/sbin/ntpdate 132.228.90.101 01 * * * * root run-parts /etc/cron.hourly 02 4 * * * root run-parts /etc/cron.daily 22 4 * * 0 root run-parts /etc/cron.weekly 42 4 1 * * root run-parts /etc/cron.monthly */1 * * * * root run-parts /opt/openoffice.org3/program/start.sh ############################################ 30 4 * * * root /usr/bin/rsync -vzrtopg --progress --delete [email protected]::resource /hyy/bak/resource 30 4 * * * root /usr/bin/rsync -vzrtopg --progress --delete [email protected]::log /hyy/bak/log ############################################ [root@localhost ~]#

(系统级的)做系统级配置我们会直接配置 /etc/crontab

(用户级的)一般还是建议大家使用 crontab -e ,这样系统也会帮着检查我们配置的脚本语法。

Crontab定时任务的可能替代品yacron 为支持Docker而设计

yacron

特点

1、这个”Crontab” 使用的是YAML格式的配置文件;

2、执行任务失败时,会发出提示邮件;

3、配置灵活: 用户可自行定义任务执行成功或失败的标准;

4、专门为在Docker,Kubernetes或其他满足12因素标准的环境中运行而设计的:

  • 前台运行;

  • 所有日志内容记录到stdout/stderr [1];

5、自动重试执行失败的任务, 会有具体的错误码返回。

[1]vixie cron只会记录日志内容到syslog,需要syslog守护进程在后台运行,否则没日志!

状态

目前该项目处于开发阶段:功能并不完善,可能会遇到部分错误或者bug。

安装

yacron 需要 Python版本 >= 3.5. 建议把yacron安装在Python的虚拟环境 , 例如:

未分类

用法

配置文件内容为YAML格式, 使用 -c参数指定配置文件或者目录进行启动。 例如:

未分类

启动yacron时(前台运行!),读取配置文件 /tmp/my-crontab.yaml , 如果参数指定的是目录, 那么目录中的 *.yaml 或者 *.yml 文件充当配置 。

基础配置

以下配置每五分钟运行一次命令:

未分类

命令可以为字符串或字符串列表。 如果命令是字符串, yacron 会通过shell运行该命令,默认是使用 /bin/sh,上面的例子中使用的是/bin/bash。

如果命令是一个字符串列表,,命令会被直接执行,,而不使用shell。 执行命令的ARGV直接从配置中提取:

未分类

schedule选项可以是传统crontab格式的字符串,或者是一个有属性的对象。以下配置每五分钟运行一次,但只在指定的时间2017-07-19运行,且不在其他任何时间运行:

未分类

可以自定义命令执行时候的环境变量:

未分类

设置默认值

配置文件的default内有个特殊块,cron任务的默认值会继承该特殊块定义的所有属性。尽管 cron任务仍可以根据需要覆盖默认值:

未分类

注意:如果配置选项为目录,并且该目录下有多个配置文件,那么每个配置文件中default仅为自己文件内的任务提动默认选项;仅在当前文件内生效。

报告

Yacron内建的报告失败任务方式(以后也许会添加其他方式)是email和sentry。(往下看,更精彩)

未分类

在上述例子, onFailure 决定当任务执行失败后所做操作。在这个例子中,我们任务失败后的操作是sentry和发送email。

captureStderr: true 部分指示yacron捕捉程序运行后的标准错误,方便将错误显示在报告中。我们也可以打开captureStdout: true 用来捕捉标准输出。默认情况,yacron仅捕捉标准错误。如果没有启用任务的标准输出或标准错误,那么这些流将简单的写入yacron本身的标准输出和标准错误。

也可以通过 onSuccess 选项来报告任务执行成功或失败。

未分类

处理异常

默认情况下,如果过程返回非零代码或生成输出标准错误(已启用标准错误捕捉)yacron 认为任务执行失败。

你可以通过 failsWhen 选项让yacron决定任务执行是否失败。

未分类

  • producesStdout
    如果为true,所有捕捉的标准输出都会让yacron认为任务执行失败。默认值为false。

  • producesStderr
    如果为true,所有捕捉的标准错误都会让yacron认为任务执行失败。默认为true。

  • nonzeroReturn
    如果为true,所有任务进程返回非零代码时,yacron认为作业失败。默认为true。

在onFailure 中加入 retry 选项,在任务执行失败的时候,yacron会进行重试失败的作业。

未分类

上述设置重试失败任务最多10次,通过指数来进行定义两次重试之间的时间间隔,第一次1秒,每次重试加倍,最大间隔(延迟)为30秒。

当任务执行失败,且重试全部失败后,onPermanentFailure 选项可以定义重试失败后的操作,如发送报告或者,放弃重试。

未分类

并发

有时候某个任务执行时间过长,当达到下一个计划执行时,可能之前的任务还未运行完成, concurrencyPolicy 选项可以帮我们解决这一问题。以下是它的三个选项:

  • Allow
    允许并发执行任务(默认为Allow)

  • Forbid
    禁止并发,如果以前的任务未完成,则跳过该任务的下一次运行

  • Replace
    取消未完成的任务,新的任务开始执行,替代原任务

执行超时

(新版本 0.4)

如果你有一个可能挂起的任务,可以通过 executionTimeout 选项来指示yacron,在N秒后结束该进程,尽管它仍在运行。例如,以下cron作业需要2秒才能完成,yacron将在1秒后终止:

未分类

当终止任务时,它会让工作过程一段时间后才能正常终止,例如,它可能已经打开了一个文件,即使你告诉它关闭,该进程可能需要几秒钟来刷新缓冲区并避免丢失数据。

另一方面,有时候程序出现异常但仅仅是被卡主,无论怎样都拒绝终止。因此,yacron会检查被要求退出的程序是否已经退出了一段时间。如果没有,将强制杀掉进程。 选项killTimeout选项指示等待进程正常终止的秒数,并强制杀掉它。在unix操作系统中,我们首先发送一个 SIGTERM信号,但是如果进程在 killTimeout秒后没有退出(默认为30),那么发送SIGKILL信号进行清理进程。例如,这个cron任务忽略了SIGTERM,所以yacron会在半秒后发送SIGKILL:

未分类

Linux Crontab定时任务使用介绍及使用saltstack管理

一、引言:

最近无意之间看到salt有一个cron的模块,今天就在这里介绍linux crontab以及通过salt的cron对crontab的管理。

二、Linux crontab的介绍:

crontab是用于设置周期性被执行的指令。该命令从标准输入设备读取指令,并将其存放在”crontab”文件中,以供之后读取和执行。crontab存储的指令被守护进程激活,crond常常在后台运行,每一分钟检查是否有预定的作业需要执行。

2.1、crond的启动与关闭:

#查看crond的状态
[root@A01-R07-I165-88 ~]# service crond status
#关闭crond
[root@A01-R07-I165-88 ~]# service crond stop
#启动crond
[root@A01-R07-I165-88 ~]# service crond start
#重启crond
[root@A01-R07-I165-88 ~]# service crond restart
#重新加载crond
[root@A01-R07-I165-88 ~]# service crond reload

2.2、全部配置文件

crontab在/etc目录下面存在cron.hourly,cron.daily,cron.weekly,cron.monthly,cron.d五个目录和crontab,cron.deny二个文件。

  • cron.daily是每天执行一次的Job;

  • cron.weekly是每个星期执行一次的Job;

  • cron.monthly是每个月执行一次的Job;

  • cron.hourly是每个小时执行一次的Job;

  • cron.d是系统自动定期需要做的任务,但是又不是按小时,按天,按星期,按月来执行的。

/etc/crontab的内容如下:

[root@A01-R07-I165-88 ~]# cat /etc/crontab 
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
#
# *  *  *  *  * user-name  command to be executed

/etc/cron.deny文件就是用于控制不让哪些用户使用crontab的功能。

2.3、用户配置文件:

每个用户都有自己得的cron配置文件,通过crontab -e就可以编辑,一般情况下我们编辑完用户的cron配置文件保存退出后,系统会自动就存放于/var/spool/cron/目录中,文件以用户名命名。

linux的cron服务是每隔一分钟去读取一次/var/spool/cron/,/etc/crontab,/etc/cron.d下面的所有内容。

2.4、cron命令格式:

crontab [ -u user ] 文件 
crontab [ -u user ] { -l | -r | -e }
-u:指定某一用户
-e:执行文字编辑器来设定用户(当前用户或指定用户)时程表,内定的文字编辑器是vi.
-r:删除用户时程表.
-l:列出用户时程表.

2.5、cron文件格式:

*  *  *  *  *  command
分 时 日 月 周   命令
第1列表示分钟1~59, 每分钟用*或者 */1表示
第2列表示小时1~23(0表示0点)
第3列表示日期1~31
第4列表示月份1~12
第5列标识号星期0~6(0表示星期天)
第6列要运行的命令

2.6、特殊用户:

[root@A01-R07-I165-88 cron]# crontab -l
# Lines below here are managed by Salt, do not edit
@reboot /root/cgroup_mkdir.sh

  
@reboot是指在开机后运行,且只运行一次,效果跟设置在/etc/rc.local中一样。

另外还有:

string            meaning 
------           ------- 
@reboot        Run once, at startup. 
@yearly         Run once a year, "0 0 1 1 *". 
@annually      (same as @yearly) 
@monthly       Run once a month, "0 0 1 * *". 
@weekly        Run once a week, "0 0 * * 0". 
@daily           Run once a day, "0 0 * * *". 
@midnight      (same as @daily) 
@hourly         Run once an hour, "0 * * * *".

2.7、每10秒运行一次:

未分类

三、通过salt来管理crontab:

新增一条记录:
salt -L '*' cron.set_job yarn '10' '9' '1' '*' '*' 'source /home/yarn/.bashrc;find /sys/fs/cgroup/cpu/hadoop-yarn -mtime +15 -name "container_*" -exec rm -rf {} ;' "rm the container files"
查看记录:
salt '*' cron.raw_cron yarn
删除记录:
salt '*' cron.rm_job yarn 'source /home/yarn/.bashrc;find /sys/fs/cgroup/cpu/hadoop-yarn -mtime +15 -name "container_*" -exec rm -rf {} ;' minute='10'

PostgreSQL CPU占用100%性能分析及慢sql优化

在数据库运维当中,一个DBA比较常遇到又比较紧急的问题,就是突发的CPU满(CPU利用率达到100%),导致业务停滞。遇到CPU满,往往需要从后端数据库开始排查,追溯到具体SQL,最终定位到业务层。下面是这个问题具体的处理方法。

查看连接数变化

CPU利用率到达100%,首先怀疑,是不是业务高峰活跃连接陡增,而数据库预留的资源不足造成的结果。我们需要查看下,问题发生时,活跃的连接数是否比平时多很多。对于RDS for PG,数据库上的连接数变化,可以从控制台的监控信息中看到。而当前活跃的连接数>可以直接连接数据库,使用下列查询语句得到:

select count( * ) from pg_stat_activity where state not like '%idle';

追踪慢SQL

如果活跃连接数的变化处于正常范围,则很大概率可能是当时有性能很差的SQL被大量执行导致。由于RDS有慢SQL日志,我们可以通过这个日志,定位到当时比较耗时的SQL来进一步做分析。但通常问题发生时,整个系统都处于停滞状态,所有SQL都慢下来,当时记录的>慢SQL可能非常多,并不容易排查罪魁祸首。这里我们介绍几种在问题发生时,即介入追查慢SQL的方法。

1、第一种方法是使用pg_stat_statements插件定位慢SQL,步骤如下。

1.1 如果没有创建这个插件,需要手动创建。我们要利用插件和数据库系统里面的计数信息(如SQL执行时间累积等),而这些信息是不断累积的,包含了历史信息。为了更方便的排查当前的CPU满问题,我们要先重置计数器。

create extension pg_stat_statements;
select pg_stat_reset();
select pg_stat_statements_reset();

1.2 等待一段时间(例如1分钟),使计数器积累足够的信息。

1.3 查询最耗时的SQL(一般就是导致问题的直接原因)。

select * from pg_stat_statements order by total_time desc limit 5;

1.4 查询读取Buffer次数最多的SQL,这些SQL可能由于所查询的数据没有索引,而导致了过多的Buffer读,也同时大量消耗了CPU。

select * from pg_stat_statements order by shared_blks_hit+shared_blks_read desc limit 5;

2、第二种方法是,直接通过pg_stat_activity视图,利用下面的查询,查看当前长时间执行,一直不结束的SQL。这些SQL对应造成CPU满,也有直接嫌疑。

select datname, usename, client_addr, application_name, state, backend_start, xact_start, xact_stay, query_start, query_stay, replace(query, chr(10), ' ') as query from (select pgsa.datname as datname, pgsa.usename as usename, pgsa.client_addr client_addr, pgsa.application_name as application_name, pgsa.state as state, pgsa.backend_start as backend_start, pgsa.xact_start as xact_start, extract(epoch from (now() - pgsa.xact_start)) as xact_stay, pgsa.query_start as query_start, extract(epoch from (now() - pgsa.query_start)) as query_stay , pgsa.query as query from pg_stat_activity as pgsa where pgsa.state != 'idle' and pgsa.state != 'idle in transaction' and pgsa.state != 'idle in transaction (aborted)') idleconnections order by query_stay desc limit 5;

3、第3种方法,是从数据表上表扫描(Table Scan)的信息开始查起,查找缺失索引的表。数据表如果缺失索引,大部分热数据又都在内存时(例如内存8G,热数据6G),此时数据库只能使用表扫描,并需要处理已在内存中的大量的无关记录,而耗费大量CPU。特别是对于表记录数超100的表,一次表扫描占用大量CPU(基本把一个CPU占满),多个连接并发(例如上百连接),把所有CPU占满。

3.1 通过下面的查询,查出使用表扫描最多的表:

select * from pg_stat_user_tables where n_live_tup > 100000 and seq_scan > 0 order by seq_tup_read desc limit 10;

3.2 查询当前正在运行的访问到上述表的慢查询:

select * from pg_stat_activity where query ilike '%<table name>%' and query_start - now() > interval '10 seconds';

3.3 也可以通过pg_stat_statements插件定位涉及到这些表的查询:

select * from pg_stat_statements where query ilike '%<table>%'order by shared_blks_hit+shared_blks_read desc limit 3;

处理慢SQL

对于上面的方法查出来的慢SQL,首先需要做的可能是Cancel或Kill掉他们,使业务先恢复:

select pg_cancel_backend(pid) from pg_stat_activity where  query like '%<query text>%' and pid != pg_backend_pid();
select pg_terminate_backend(pid) from pg_stat_activity where  query like '%<query text>%' and pid != pg_backend_pid();

如果这些SQL确实是业务上必需的,则需要对他们做优化。这方面有“三板斧”:

1、对查询涉及的表,执行ANALYZE <table>或VACUUM ANZLYZE <table>,更新表的统计信息,使查询计划更准确。注意,为避免对业务影响,最好在业务低峰执行。

2、执行explain (query text)或explain (buffers true, analyze true, verbose true) (query text)命令,查看SQL的执行计划(注意,前者不会实际执行SQL,后者会实际执行而且能得到详细的执行信息),对其中的Table Scan涉及的表,建立索引。

3、重新编写SQL,去除掉不必要的子查询、改写UNION ALL、使用JOIN CLAUSE固定连接顺序等到,都是进一步深度优化SQL的手段,这里不再深入说明。

使用docker-swarm创建docker高可用集群

Swarm概念

Swarm是Docker公司推出的用来管理docker集群,它将一群Docker宿主机变成一个单一的,虚拟的主机。Swarm使用标准的Docker API接口作为其前端访问入口,换言之,各种形式的Docker Client(docker client in Go, docker_py, docker等)均可以直接与Swarm通信。Swarm几乎全部用go语言来完成开发,Swarm0.2发布,相比0.1版本,0.2版本增加了一个新的策略来调度集群中的容器,使得在可用的节点上传播它们,以及支持更多的Docker命令以及集群驱动。
  
Swarm deamon只是一个调度器(Scheduler)加路由器(router),Swarm自己不运行容器,它只是接受docker客户端发送过来的请求,调度适合的节点来运行容器,这意味着,即使Swarm由于某些原因挂掉了,集群中的节点也会照常运行,当Swarm重新恢复运行之后,它会收集重建集群信息.

Swarm结构图

未分类

Swarm的基本命令

docker node 用来显示集群的节点,默认建立时只有一个节点,当然也就谈不上高可用了,可以使用docker node –help来查看所有node参数

未分类

集群初始化 docker swarm init

未分类

当已经被初始化后,就不能重新执行这个操作了,使用docker node ls 来查看刚建立的集群

集群中的管理节点和工作节点功能图

未分类

添加管理节点 docker swarm join

Docker Swarm 命令中还需要添加一些选项:

  • join:表明一个新的节点将被添加进 Swarm

  • –manager:表明节点的性质(manager vs worker)

  • –listen-addr:让一个新添加的节点可以访问 Swarm 内的其他节点

  • 最后的参数就是第一管理节点的地址(即这一命令将被送到的那个节点)

注意:由于 –auto-accept manager 选项会在 Swarm 初始化的过程中被提供,所以第二管理节点会被自动接受。如果没有这一选项,那么第二管理节点需要被第一管理节点手动接受。

$ MANAGER2_IP=$(docker-machine ip manager2)
docker-machine ssh manager2 docker swarm join --manager --listen-addr $MANAGER2_IP:2377 $MANAGER1_IP:2377

Swarn部署时使用的脚本,来自网络

下面是一小段用来创建 Docker 主机并部署 Swarm 的 Shell 脚本。当然了,管理/工作节点的数字都是可以随意改动的。
注意:创建两个管理节点和两个工作节点,仅仅是用来作示范。在工业生产中,我们可能需要在集群里搭建 3 个管理节点和 5 个工作节点。

# Define the number of managers/workers
MANAGER=3
WORKER=5

# Create the Docker hosts
for i in $(seq 1 $MANAGER); do docker-machine create --driver virtualbox manager$i; done
for i in $(seq 1 $WORKER); do docker-machine create --driver virtualbox worker$i; done

# Init the swarm
docker-machine ssh manager1 docker swarm init --auto-accept manager --auto-accept worker --listen-addr $(docker-machine ip manager1):2377

# Add additional manager(s)
for i in $(seq 2 $MANAGER); do docker-machine ssh manager$i docker swarm join --manager --listen-addr $(docker-machine ip manager$i):2377 $(docker-machine ip manager1):2377; done

# Add workers
for i in $(seq 1 $WORKER); do docker-machine ssh worker$i docker swarm join --listen-addr $(docker-machine ip worker$i):2377 $(docker-machine ip manager1):2377; done

对于上面文章中,只提到了集群,而没有谈到如何去使用,在建立集群后,服务的部署我们可以用

docker stack deploy  -c test.yml test

下面给出自己写的一个服务,版本3的

version: "3"

services:
  loggerapi:
    image: logger.api
    build:
      context: ./src/Logger.Api
      dockerfile: Dockerfile
    ports:
      - "5000:80"
    networks:
      - ingress

 loggermanager:
    image: logger.manager
    build:
      context: ./src/Logger.Manager
      dockerfile: Dockerfile
    ports:
      - "5050:80"
    networks:
      - ingress

networks:
  ingress:

这里有个服务要注意,服务的名称一定不能有点,如logger.manager这是错误的!

未分类

来建立一个服务,同时可以使用docker service来查看已经运行的服务!

还有一点要注意,yml在进行v3版后,不再支持build,也就是说,你需要先把镜像建立好才行!

未分类

sqlalchemy增删改查及关系使用介绍

1. SQLAlchemy的作用

ORM对象关系映射技术

2. SQLAlchemy安装

pip install SQLAlchemy

查看SQLAlchemy版本

3. 声明模型Model

from sqlalchemy import Column,String,Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__='user'
    id=Column(Integer,primary_key=True)
    name=Column(String(50))

基类Base,是一个model和数据库表管理类。
通过继承Base,可自动实现model和数据库表的关联。

4. 创建数据库表

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from user import User
if __name__ == "__main__":
    engine=create_engine("postgresql://postgres:sj1107@localhost:5432/sampledb")
    Session=sessionmaker(bind=engine)
    session=Session()
    User.metadata.create_all(engine)

engine:数据库连接引擎
Session:一个持久的数据库连接会话
User.metadata.create_all(engine):创建表

5. 增删改查

session是关系型数据库中的事务。

5.1 增加记录

user=User(name="shijingjing07")
session.add(user)
session.commit()

必须commit,才能真正写入数据库

5.2 删除记录

usr=session.query(User).first()
session.delete(usr)
session.commit()

5.3 更新记录

usr=session.query(User).first()
usr.name="icefinger"
session.add(usr)
session.commit()

5.4 查询记录

过滤器:

#==
usr=session.query(User).filter(User.name=="icefinger").first()
print(usr.id)
#!=
usr=session.query(User).filter(User.name!="icefinger").first()
print(usr.id)
#like
usr=session.query(User).filter(User.name.like("icefinger%")).first()
print(usr.id)
#in
usr=session.query(User).filter(User.name.in_(["icefinger","tomcat","james"])).first()
print(usr.id)
#not in
usr=session.query(User).filter(~User.name.in_(["icefinger","tomcat","james"])).first()
print(usr.id)
#is null
usr=session.query(User).filter(User.name==None).first()
print(usr.id)
usr=session.query(User).filter(User.name.is_(None)).first()
print(usr.id)
#and
from sqlalchemy import and_
usr=session.query(User).filter(and_(User.name=="icefinger",User.id=="2")).first()
print(usr.id)
#or
from sqlalchemy import or_
usr=session.query(User).filter(or_(User.name=="icefinger",User.id=="3")).first()
print(usr.id)

返回值:

#first,使用limit返回第一行
print("--first--")
usr=session.query(User).filter(or_(User.name=="icefinger",User.id=="3")).first()
print(usr.id)
#all,返回所有行
print("--all--")
usrlist=session.query(User).filter(or_(User.name=="icefinger",User.id=="3")).all()
for usr in usrlist:
    print(usr.id)
#one,返回行数只能是一条
print("--one--")
try:
    usr = session.query(User).filter(or_(User.name == "icefinger", User.id == "3")).one()
    print(usr)
except:
    print("must be one")
#one_on_none,返回行数只能是一条,或none
print("--one_or_none--")
usr = session.query(User).filter(and_(User.name == "icefinger", User.id == "2")).one_or_none()
print(usr)
#scalar,同one_on_none,返回行数只能是一条,或none
print("--scalar--")
usr = session.query(User).filter(or_(User.name == "icefinger", User.id == "2")).scalar()
print(usr)

运行结果:

未分类

统计个数:

print("--count1--")
count=session.query(User).count()
print(count)
print("--count2--")
count = session.query(func.count('*')).select_from(User).scalar()
print(count)

6. 关系-一对多

6.1 如下图所示,一个用户可能对应多个地址

from sqlalchemy import create_engine,and_,or_,func
from sqlalchemy import Table,Column,String,Integer,ForeignKey
from sqlalchemy.orm import relationship,sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine=create_engine("postgresql://postgres:sj1107@localhost:5432/sampledb")
Session=sessionmaker(bind=engine)
session = Session()
Base = declarative_base(bind=engine)
class User(Base):
    __tablename__='user'
    id=Column(Integer,primary_key=True)
    addresses=relationship('Address')
class Address(Base):
    __tablename__='address'
    id=Column(Integer,primary_key=True)
    user_id=Column(Integer,ForeignKey('user.id'))

if __name__ == "__main__":
    Base.metadata.create_all()
    u=User()
    session.add(u)
    session.commit()
    a1=Address(user_id=u.id)
    a2=Address(user_id=u.id)
    session.add(a1)
    session.add(a2)
    session.commit()
    print(u.addresses)

运行结果:

未分类

ForeignKey:外键,制定了user_id和User的关系
relationship:绑定了两个Model的联系,通过User直接得到所有的地址。

6.2 根据address获取user

address只能获得user_id,然后根据user_id获取user
能不能通过address直接获取user呢?在model里,添加relationship关系就可以了。

class User(Base):
    __tablename__='user'
    id=Column(Integer,primary_key=True)
    addresses=relationship('Address')
class Address(Base):
    __tablename__='address'
    id=Column(Integer,primary_key=True)
    user_id=Column(Integer,ForeignKey('user.id'))
    user=relationship('User')

运行结果:

未分类

6.3 上例中两个model中都添加relationship,看起来很繁琐,能不能只指定一个,另一个默认就可以访问呢?

backref参数就可以了。

class User(Base):
    __tablename__='user'
    id=Column(Integer,primary_key=True)
    addresses=relationship('Address',backref="user")
class Address(Base):
    __tablename__='address'
    id=Column(Integer,primary_key=True)
    user_id=Column(Integer,ForeignKey('user.id'))

运行结果:

未分类

7. 关系-多对多

user和address关系为多对多,即一个user对应多个address,一个address对应多个user
多对多需要中间表来关联

#定义中间表,关联多对多关系
user_address_table =Table(
    'user_address',Base.metadata,
    Column('user_id',Integer,ForeignKey('user.id')),
    Column('address_id',Integer,ForeignKey('address.id'))
)
class User(Base):
    __tablename__='user'
    id=Column(Integer,primary_key=True)
    addresses=relationship('Address',secondary=user_address_table)
class Address(Base):
    __tablename__='address'
    id=Column(Integer,primary_key=True)
    users=relationship('User',secondary=user_address_table)

if __name__ == "__main__":
    # Base.metadata.create_all()
    u1=User()
    u2=User()
    session.add(u1)
    session.add(u2)
    session.commit()
    a1=Address(users=[u1,u2])
    a2 = Address(users=[u1, u2])
    session.add(a1)
    session.add(a2)
    session.commit()
    print(u1.addresses)
    print(a1.users)
    session.delete(u1)
    print(a1.users)

运行结果:

未分类

PostgreSQL数据库修改表增加主键

PostgreSQL数据库测试环境中有多张表没有添加主键约束,只有一个serial的自增字段。现在需要把那些没有主键的表都加上,serial类型的字段为id 。

首先是怎么找到PostgreSQL数据库中哪些表没有主键?我们看下pg_class这个表,里面有个relhaspkey字段,如果为t说明有主键,f即没有主键。例如下面这个sql 。

SELECT n.nspname AS "Schema",c.relname AS "Table Name",c.relhaspkey AS "Has PK" 
FROM
 pg_catalog.pg_class c
JOIN
 pg_namespace n
ON (
 c.relnamespace = n.oid
 AND n.nspname NOT IN ('information_schema', 'pg_catalog')
 AND c.relkind='r'
)
WHERE c.relhaspkey = 'f'
ORDER BY c.relhaspkey, c.relname
;

然后就是对这些表增加主键约束。删除和添加主键的sql如下所示:

alter table server drop constraint server_pkey ;
alter table server add primary key (id) ;

主键添加完成之后可以通过d查看。

zhangnq=# d server
 Table "public.server"
 Column | Type | Modifiers 
--------+---------------+------------------------------------------------------
 id | integer | not null default nextval('server_int_seq'::regclass)
 ip | character(50) | 
Indexes:
 "server_pkey" PRIMARY KEY, btree (id)

最后就是把这个思路写到脚本里面,运行脚本批量添加。脚本里面把执行失败的表都放在error.log文件中。

脚本:

#!/bin/bash
export PATH=/opt/PostgreSQL/93/bin:$PATH
export PGDATA=/data/pgsql
export PGHOME=/opt/PostgreSQL/93
export PGPORT=5432
dbname=$1
if [ ! $dbname ];then
 echo "Please enter the database name."
 exit 1
fi
psql -c "dt" -d $dbname >/dev/null
if [ $? -ne 0 ];then
 exit 1
fi
error_log="error.log"
echo "">$error_log
sql=`cat << EOF
SELECT n.nspname AS "Schema",c.relname AS "Table Name"
FROM
 pg_catalog.pg_class c
JOIN
 pg_namespace n
ON (
 c.relnamespace = n.oid
 AND n.nspname NOT IN ('information_schema', 'pg_catalog')
 AND c.relkind='r'
)
WHERE c.relhaspkey = 'f'
ORDER BY c.relhaspkey, c.relname
;
EOF`
schemas=`psql -t -A -c "$sql" -d $dbname |cut -d "|" -f 1`
tables=`psql -t -A -c "$sql" -d $dbname |cut -d "|" -f 1`
for res in `psql -t -A -c "$sql" -d $dbname`
do
 schema=`echo $res|cut -d "|" -f 1`
 table=`echo $res|cut -d "|" -f 2`
 tablename=`echo "$schema.$table"`
 psql -e -c "alter table $tablename add primary key (id) " -d $dbname
 if [ $? -ne 0 ];then
 echo "$dbname : Add primary key to $tablename error." >>$error_log
 fi
done

说下碰到的的问题,在测试的时候发现如果把主键drop掉之后pg_class.relhaspkey值还是为t,但是用d查看确实没有主键了。解决的办法是手动vacuum这个表,即vacuum server 。

zhangnq=# select relname,relhaspkey from pg_class where relname='server' ;
 relname | relhaspkey 
---------+------------
 server | t
(1 row)
zhangnq=# alter table server drop constraint server_pkey ;
ALTER TABLE
zhangnq=# select relname,relhaspkey from pg_class where relname='server' ;
 relname | relhaspkey 
---------+------------
 server | t
(1 row)
zhangnq=# vacuum server ;
VACUUM
zhangnq=# select relname,relhaspkey from pg_class where relname='server' ;
 relname | relhaspkey 
---------+------------
 server | f
(1 row)
zhangnq=# alter table server add primary key (id) ;
ALTER TABLE
zhangnq=# select relname,relhaspkey from pg_class where relname='server' ;
 relname | relhaspkey 
---------+------------
 server | t
(1 row)

查看pg_class的说明后发现原来pg_class只有在状态由false变成ture的时候会自动修改。这么设计可以提高并发性。

Several of the Boolean flags in pg_class are maintained lazily: they are guaranteed to be true if that's the correct state, but may not be reset to false immediately when the condition is no longer true. For example, relhasindex is set by CREATE INDEX, but it is never cleared by DROP INDEX. Instead, VACUUM clears relhasindex if it finds the table has no indexes. This arrangement avoids race conditions and improves concurrency.