月度归档:2017年04月

时间同步客户端 – Chrony

Centos7.x中已经默认安装此时间同步客户端。

安装Chrony:

#ubuntu/debian:
apt-get install chrony

#Centos/redhat
yum install chrony

时间服务器删除添加:

#删除默认的Server
sed -i "/server/d" /etc/chrony.conf

#添加
#打开/etc/chrony.conf,新增一行:
server ntp.aliyun.com iburst

重启并检查:

/etc/init.d/chronyd restart
#或
systemctl restart chronyd

#查看是否正常
chronyc tracking

Reference ID    : CB6B0658 (203.107.6.88)
Stratum         : 3
Ref time (UTC)  : Mon Mar 12 02:01:35 2018
System time     : 0.000003723 seconds fast of NTP time
Last offset     : -0.003573318 seconds
RMS offset      : 0.003573318 seconds
Frequency       : 1.765 ppm slow
Residual freq   : +55.304 ppm
Skew            : 6.448 ppm
Root delay      : 0.064887173 seconds
Root dispersion : 0.001230678 seconds
Update interval : 2.1 seconds
Leap status     : Normal

其它说明—————————————————————–

rpm -qa | grep chrony
chrony-2.1.1-4.el7.centos.x86_64

rpm -ql chrony-2.1.1-4.el7.centos.x86_64
/etc/NetworkManager/dispatcher.d/20-chrony
/etc/chrony.conf   *****
/etc/chrony.keys
/etc/dhcp/dhclient.d/chrony.sh
/etc/logrotate.d/chrony
/usr/bin/chronyc  *****
/usr/lib/systemd/ntp-units.d/50-chronyd.list
/usr/lib/systemd/system/chrony-dnssrv@.service
/usr/lib/systemd/system/chrony-dnssrv@.timer
/usr/lib/systemd/system/chrony-wait.service
/usr/lib/systemd/system/chronyd.service
/usr/libexec/chrony-helper
/usr/sbin/chronyd  *****
/var/lib/chrony
/var/lib/chrony/drift
/var/lib/chrony/rtc
/var/log/chrony

主要三个文件,/etc/chrony.conf配置,/usr/bin/chronyc是客户端工具,/usr/sbin/chronyd是后台进程(使用/usr/lib/systemd/system/chronyd.service来管理)。

当Chrony启动时,它会读取/etc/chrony.conf配置文件中的设置。配置参数:

1 server – 该参数可以多次用于添加时钟服务器,必须以”server “格式使用。一般而言,你想添加多少服务器,就可以添加多少服务器。

    server 0.centos.pool.ntp.org
    server 3.europe.pool.ntp.org

2 stratumweight – stratumweight指令设置当chronyd从可用源中选择同步源时,每个层应该添加多少距离到同步距离。默认情况下,CentOS中设置为0,让chronyd在选择源时忽略源的层级。

3 driftfile – chronyd程序的主要行为之一,就是根据实际时间计算出计算机增减时间的比率,将它记录到一个文件中是最合理的,它会在重启后为系统时钟作出补偿,甚至可能的话,会从时钟服务器获得较好的估值。

4 rtcsync – rtcsync指令将启用一个内核模式,在该模式中,系统时间每11分钟会拷贝到实时时钟(RTC)。

5 allow / deny – 这里你可以指定一台主机、子网,或者网络以允许或拒绝NTP连接到扮演时钟服务器的机器。

    allow 192.168.4.5
    deny 192.168/16

6 cmdallow / cmddeny – 跟上面相类似,只是你可以指定哪个IP地址或哪台主机可以通过chronyd使用控制命令

7 bindcmdaddress – 该指令允许你限制chronyd监听哪个网络接口的命令包(由chronyc执行)。该指令通过cmddeny机制提供了一个除上述限制以外可用的额外的访问控制等级。

    bindcmdaddress 127.0.0.1
    bindcmdaddress ::1

8 makestep – 通常,chronyd将根据需求通过减慢或加速时钟,使得系统逐步纠正所有时间偏差。在某些特定情况下,系统时钟可能会漂移过快,导致该调整过程消耗很长的时间来纠正系统时钟。该指令强制chronyd在调整期大于某个阀值时步进调整系统时钟,但只有在因为chronyd启动时间超过指定限制(可使用负值来禁用限制),没有更多时钟更新时才生效。

也可以通过运行chronyc命令来修改设置,命令如下:
1 accheck – 检查NTP访问是否对特定主机可用
2 activity – 该命令会显示有多少NTP源在线/离线

chronyc> activity 
200 OK
4 sources online
0 sources offline
0 sources doing burst (return to online)
0 sources doing burst (return to offline)
0 sources with unknown address

3 add server – 手动添加一台新的NTP服务器。
4 clients – 在客户端报告已访问到服务器
5 delete – 手动移除NTP服务器或对等服务器
6 settime – 手动设置守护进程时间
7 tracking – 显示系统时间信息

chronyc> tracking
Reference ID    : 38.126.113.10 (38.126.113.10)
Stratum         : 3
Ref time (UTC)  : Mon Apr 24 06:29:55 2017
System time     : 0.000244395 seconds slow of NTP time
Last offset     : -0.000066831 seconds
RMS offset      : 0.000231604 seconds
Frequency       : 18.263 ppm fast
Residual freq   : -0.038 ppm
Skew            : 0.126 ppm
Root delay      : 0.012766 seconds
Root dispersion : 0.007974 seconds
Update interval : 518.7 seconds
Leap status     : Normal

CentOS 7.x 升级内核

默认,CentOS 7.x下,运行yum update会自动升级内核,不过一般都是非常保守的升级:

yum list kernel*
已安装的软件包
kernel.x86_64      3.10.0-514.el7                                         @anaconda     
kernel.x86_64      3.10.0-514.10.2.el7                                    @updates      
kernel.x86_64      3.10.0-514.16.1.el7                                    @updates 

这里可以看到,默认安装的是3.10.0-514.el7, 后来更新两次,分别安装了3.10.0-514.10.2.el7和3.10.0-514.16.1.el7。可见,内核版本号并没有发生任何改变,只是横杆后(-)的数字发生改变,这个应该是发行商维护的(非Linux内核维护),主要应该是一些补丁包添加。

如果要升级到最新的Linux内核,可以去kernel.org去下载源代码编译,不过随着内核越来越复杂,编译涉及到的内容非常多,所以注定是一件费时费力的事情。

首先了解一下kernel.org提供的Linux内核的版本(https://www.kernel.org/category/releases.html)。简单来说,RC版本就是预发布版本,Mainline主线版本,一般2-3月发布一次。Mainline之后就会释放稳定(Stable)版本。还有Longterm版本,表示是长期支持的版本。大部分发行版本Linux都会考虑使用Longterm版本的内核,比如CentOS中就使用3.10.0这个版本。

Longterm release kernels
Version Maintainer Released Projected EOL
4.9 Greg Kroah-Hartman 2016-12-11 Jan, 2019
4.4 Greg Kroah-Hartman 2016-01-10 Feb, 2018
4.1 Sasha Levin 2015-06-21 Sep, 2017
3.16 Ben Hutchings 2014-08-03 Apr, 2020
3.12 Jiri Slaby 2013-11-03 May, 2017
3.10 Willy Tarreau 2013-06-30 Oct, 2017
3.4 Li Zefan 2012-05-20 Apr, 2017
3.2 Ben Hutchings 2012-01-04 May, 2018

从这个表格看,3.10.0这个内核从2013年开始发布,到现在(2017年)已几年过去了,看起来稍微是有点旧了。而且2017年以后,至少需要使用4.4的内核。

为了便利地升级内核,可以使用elrepo(http://elrepo.org)这个软件源,以yum的方法快速安装:

# import key
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
# install elrepo repo
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
# install kernel
yum --enablerepo=elrepo-kernel install  kernel-ml-devel kernel-ml -y
# modify grub
grub2-set-default 0
# reboot
reboot

以上安装的是最新的稳定版。源elrepo提供两个内核,kernel-ml中的ml是指mainline, 是mainline后的最新稳定版本;还有一个叫kernel-lt,它是基于长期支持版本的,比如当前kernel-lt就是指4.4版本的内核。

可以浏览如下链接,查看具体的软件包:
http://elrepo.org/linux/kernel/el7/x86_64/RPMS/

要确定可以使用哪些内核,可以使用:

yum --enablerepo=elrepo-kernel list kernel*

关于Grub2:
“Briefly, a boot loader is the first software program that runs when a computer starts. It is responsible for loading and transferring control to an operating system kernel software (such as Linux or GNU Mach). The kernel, in turn, initializes the rest of the operating system (e.g. a GNU system). ”

查看下安装:

rpm -qa | grep grub2
grub2-tools-2.02-0.44.el7.centos.x86_64
grub2-2.02-0.44.el7.centos.x86_64

rpm -ql grub2-2.02-0.44.el7.centos.x86_64
/boot/grub2/grub.cfg
/boot/grub2/grubenv
/etc/grub2.cfg  #软连接到/boot/grub2/grub.cfg

rpm -ql grub2-tools-2.02-0.44.el7.centos.x86_64
/etc/default/grub

/etc/grub.d
/etc/grub.d/00_header
/etc/grub.d/01_users
/etc/grub.d/10_linux
/etc/grub.d/20_linux_xen
/etc/grub.d/20_ppc_terminfo
/etc/grub.d/30_os-prober
/etc/grub.d/40_custom
/etc/grub.d/41_custom
/etc/grub.d/README

/etc/prelink.conf.d/grub2.conf
/etc/sysconfig/grub -> /etc/default/grub

/usr/bin/grub2-editenv
/usr/bin/grub2-file
/usr/bin/grub2-fstest
/usr/bin/grub2-glue-efi
/usr/bin/grub2-kbdcomp
/usr/bin/grub2-menulst2cfg
/usr/bin/grub2-mkfont
/usr/bin/grub2-mkimage
/usr/bin/grub2-mklayout
/usr/bin/grub2-mknetdir
/usr/bin/grub2-mkpasswd-pbkdf2
/usr/bin/grub2-mkrelpath
/usr/bin/grub2-mkrescue
/usr/bin/grub2-mkstandalone
/usr/bin/grub2-render-label
/usr/bin/grub2-script-check
/usr/bin/grub2-syslinux2cfg

/usr/sbin/grub2-bios-setup
/usr/sbin/grub2-get-kernel-settings
/usr/sbin/grub2-install
/usr/sbin/grub2-macbless
/usr/sbin/grub2-mkconfig
/usr/sbin/grub2-ofpathname
/usr/sbin/grub2-probe
/usr/sbin/grub2-reboot
/usr/sbin/grub2-rpm-sort
/usr/sbin/grub2-set-default
/usr/sbin/grub2-setpassword
/usr/sbin/grub2-sparc64-setup

Grub简单来说就是一个引导程序。机器子上电自检后,会从启动硬盘寻找引导程序,控制权交给引导程序后,由它负责系统引导,最关键的任务就是从什么地方载入内核。

配置文件/etc/grub2.cfg是一个软连接,指向/boot/grub2/grub.cfg,这个是主要的配置文件,其中有启动菜单项(多个,第一个标号为0,第二个为1,依次类推):

menuentry 'CentOS Linux (4.10.12-1.el7.elrepo.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-514.el7.x86_64-advanced-3f38705e-ad35-43b8-b624-4f021e320c87' {
	load_video
	set gfxpayload=keep
	insmod gzio
	insmod part_msdos
	insmod xfs
	set root='hd0,msdos1'
	if [ x$feature_platform_search_hint = xy ]; then
	  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos1 --hint-efi=hd0,msdos1 --hint-baremetal=ahci0,msdos1 --hint='hd0,msdos1'  f89b61bd-58b6-48c5-98db-ec8a00d84a3e
	else
	  search --no-floppy --fs-uuid --set=root f89b61bd-58b6-48c5-98db-ec8a00d84a3e
	fi
	linux16 /vmlinuz-4.10.12-1.el7.elrepo.x86_64 root=/dev/mapper/cl-root ro crashkernel=auto rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet LANG=zh_CN.UTF-8
	initrd16 /initramfs-4.10.12-1.el7.elrepo.x86_64.img
}

一般,升级内核后会自动添加一个菜单项(在第0个位置)。默认,Grub取哪个菜单引导,取决于/boot/grub2/grubenv中的saved_entry配置:

saved_entry=0

这样表示取排在第0个位置的菜单作为默认引导。

也可以直接是菜单项名称,比如:

saved_entry=CentOS Linux (4.10.12-1.el7.elrepo.x86_64) 7 (Core)

可以直接编辑/boot/grub2/grubenv中的saved_entry,也可以使用工具grub2-editenv来编辑/boot/grub2/grubenv,不过如果仅仅想改变saved_entry这个环境变量,则可以使用grub2-set-default来进行,比如grub2-set-default 0就是把/boot/grub2/grubenv中的saved_entry设置为0。

为了防止载入不确定的内核(比如编辑了菜单),一般不建议使用索引号,可以如下设置,锁定默认到指定菜单:

grub2-set-default 'CentOS Linux (4.10.12-1.el7.elrepo.x86_64) 7 (Core)'

HTML编辑器 – UEditor

UEditor 是一个功能比较齐全的HTML编辑器,百度出品,官方网站为:http://ueditor.baidu.com/website/。这个编辑器对于文件上传方面提供了强大支持,比如可以直接粘贴(CTRL + V)图片,上传涂鸦文件(客户端画图),多文件上传等。

其中有一个叫UMeditor的产品,简称UM,它是UEditor(简称UE)的功能缩减版本。也可以使用其提供的Ubuilder来构建一个自定义的版本(主要指功能模块)。

一般直接使用UEditor这个全功能版本即可:
ueditor

目录结构:
ueditor-struct
目录dialogs对应各种弹框,目录lang对应语言包(前端展示),目录themes对应皮肤,third-party是第三方插件。文件ueditor.all.js是编辑器实现代码,可见未压缩有1M以上,min压缩后也有接近400K,问津ueditor.config.js是前端的主要配置。

前台主要配置都在ueditor.config.js中,这个文件几乎不用改变,一般需要修改的地方就是serverUrl参数,当有图片或文件需要上传时,就提交到这个指定的地址。其它配置项在这个文件中都有详细的注释(比如皮肤,语言等):

(function () {
    var URL = window.UEDITOR_HOME_URL || getUEBasePath();
    var origin = getOrigin();

    /**
     * 配置项主体。注意,此处所有涉及到路径的配置别遗漏URL变量。
     */
    window.UEDITOR_CONFIG = {

        //为编辑器实例添加一个路径,这个不能被注释
        UEDITOR_HOME_URL: URL

        // 服务器统一请求接口路径
        , serverUrl: origin + "/ueditor/server"
    };
    // ...
    // ...
    function getOrigin() {
        if (typeof location.origin === 'undefined') {
            location.origin = location.protocol + '//' + location.host;
        }
        return location.origin;
    }

    window.UE = {
        getUEBasePath: getUEBasePath,
        getOrigin: getOrigin
    };
})();

其中getOrigin()原始文件是没有的,这个用来解决原来的函数获取基本路径不准确的问题。

以上配置指定了/ueditor/serve,那么文件上传时都会POST到这个地址。所以后端需要处理文件上传的逻辑,原本下载包已经提供了相关的实现,不过为了真正可用,需要做一些改造,比如需要验证:

// 抽象类,Upload, 处理文件上传逻辑,有上个子类:UploadCatch.php 、 UploadFile.php 、 UploadScrawl.php
// 分别实现fire()方法,这个方法实际是读取配置,处理上传逻辑
<?php
namespace UEditor;

abstract class Upload
{
    //文件域名
    protected $fileField;
    //文件上传对象
    protected $file;
    //文件上传对象
    protected $base64;
    //配置信息
    protected $config;
    //原始文件名
    protected $oriName;
    //新文件名
    protected $fileName;
    //完整文件名,即从当前配置目录开始的URL
    protected $fullName;
    //完整文件名,即从当前配置目录开始的URL
    protected $filePath;
    //文件大小
    protected $fileSize;
    //文件类型
    protected $fileType;
    //上传状态信息,
    protected $stateInfo;
    //上传状态映射表,国际化用户需考虑此处数据的国际化
    protected $stateMap = array(
        //上传成功标记,在UEditor中内不可改变,否则flash判断会出错
        "SUCCESS",
        "文件大小超出 upload_max_filesize 限制",
        "文件大小超出 MAX_FILE_SIZE 限制",
        "文件未被完整上传",
        "没有文件被上传",
        "上传文件为空",
        "ERROR_TMP_FILE" => "临时文件错误",
        "ERROR_TMP_FILE_NOT_FOUND" => "找不到临时文件",
        "ERROR_SIZE_EXCEED" => "文件大小超出网站限制",
        "ERROR_TYPE_NOT_ALLOWED" => "文件类型不允许",
        "ERROR_CREATE_DIR" => "目录创建失败",
        "ERROR_DIR_NOT_WRITEABLE" => "目录没有写权限",
        "ERROR_FILE_MOVE" => "文件保存时出错",
        "ERROR_FILE_NOT_FOUND" => "找不到上传文件",
        "ERROR_WRITE_CONTENT" => "写入文件内容错误",
        "ERROR_UNKNOWN" => "未知错误",
        "ERROR_DEAD_LINK" => "链接不可用",
        "ERROR_HTTP_LINK" => "链接不是http链接",
        "ERROR_HTTP_CONTENTTYPE" => "链接contentType不正确",
        "INVALID_URL" => "非法 URL",
        "INVALID_IP" => "非法 IP"
    );

    public function __construct(array $config, $request)
    {
        $this->config = $config;
        $this->request = $request;
        $this->fileField = $this->config['fieldName'];
        if (isset($config['allowFiles'])) {
            $this->allowFiles = $config['allowFiles'];
        } else {
            $this->allowFiles = [];
        }
    }

    abstract function fire();

    public function handle()
    {
        $this->fire();
        return $this->getFileInfo();
    }

    /**
     * 上传错误检查
     * @param $errCode
     * @return string
     */
    protected function getStateInfo($errCode)
    {
        return !$this->stateMap[$errCode] ? $this->stateMap["ERROR_UNKNOWN"] : $this->stateMap[$errCode];
    }

    /**
     * 文件大小检测
     * @return bool
     */
    protected function checkSize()
    {
        return $this->fileSize <= ($this->config["maxSize"]);
    }

    /**
     * 获取文件扩展名
     * @return string
     */
    protected function getFileExt()
    {
        return '.' . $this->file->guessExtension();
    }

    /**
     * 重命名文件
     * @return string
     */
    protected function getFullName()
    {
        //替换日期事件
        $t = time();
        $d = explode('-', date("Y-y-m-d-H-i-s"));
        $format = $this->config["pathFormat"];
        $format = str_replace("{yyyy}", $d[0], $format);
        $format = str_replace("{yy}", $d[1], $format);
        $format = str_replace("{mm}", $d[2], $format);
        $format = str_replace("{dd}", $d[3], $format);
        $format = str_replace("{hh}", $d[4], $format);
        $format = str_replace("{ii}", $d[5], $format);
        $format = str_replace("{ss}", $d[6], $format);
        $format = str_replace("{time}", $t, $format);

        //过滤文件名的非法自负,并替换文件名
        $oriName = substr($this->oriName, 0, strrpos($this->oriName, '.'));
        $oriName = preg_replace("/[\|\?\"\<\>\/\*\\\\]+/", '', $oriName);
        $format = str_replace("{filename}", $oriName, $format);

        //替换随机字符串
        $randNum = rand(1, 10000000000) . rand(1, 10000000000);
        if (preg_match("/\{rand\:([\d]*)\}/i", $format, $matches)) {
            $format = preg_replace("/\{rand\:[\d]*\}/i", substr($randNum, 0, $matches[1]), $format);
        }

        $ext = $this->getFileExt();
        return $format . $ext;
    }

    /**
     * 获取文件完整路径
     * @return string
     */
    protected function getFilePath()
    {
        $fullName = $this->fullName;
        $rootPath = public_path();
        $fullName = ltrim($fullName, '/');

        return $rootPath . '/' . $fullName;
    }

    /**
     * 文件类型检测
     * @return bool
     */
    protected function checkType()
    {
        return in_array($this->getFileExt(), $this->config["allowFiles"]);
    }

    /**
     * 获取当前上传成功文件的各项信息
     * @return array
     */
    public function getFileInfo()
    {
        return array(
            "state" => $this->stateInfo,
            "url" => $this->fullName,
            "title" => $this->fileName,
            "original" => $this->oriName,
            "type" => $this->fileType,
            "size" => $this->fileSize
        );
    }
}


// 文件上传
class UploadFile extends Upload
{
    public function fire()
    {
        $file = $this->request->file($this->fileField);
        if (empty($file)) {
            $this->stateInfo = $this->getStateInfo("ERROR_FILE_NOT_FOUND");
            return false;
        }
        if (!$file->isValid()) {
            $this->stateInfo = $this->getStateInfo($file->getError());
            return false;
        }

        $this->file = $file;
        $this->oriName = $this->file->getClientOriginalName();
        $this->fileSize = $this->file->getSize();
        $this->fileType = $this->getFileExt();
        $this->fullName = $this->getFullName();
        $this->filePath = $this->getFilePath();
        $this->fileName = basename($this->filePath);

        //检查文件大小是否超出限制
        if (!$this->checkSize()) {
            $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED");
            return false;
        }
        //检查是否不允许的文件格式
        if (!$this->checkType()) {
            $this->stateInfo = $this->getStateInfo("ERROR_TYPE_NOT_ALLOWED");
            return false;
        }

        if (config('ueditor.drivers.default') == 'local') {
            try {
                $this->file->move(dirname($this->filePath), $this->fileName);
                $this->stateInfo = $this->stateMap[0];
            } catch (\Symfony\Component\HttpFoundation\File\Exception\FileException $exception) {
                $this->stateInfo = $this->getStateInfo("ERROR_WRITE_CONTENT");
                return false;
            }
        } else {
            $this->stateInfo = $this->getStateInfo("ERROR_UNKNOWN");
            return false;
        }
        return true;
    }
}

要真正可用,还需要结合具体使用的框架做配置,比如在Laravel框架中,经过一些改造,就可以继承到项目中,如下是实际使用部署:
ueditor.zip

至于使用,则非常简单,需要编辑器的地方引入:

<script type="text/javascript" charset="utf-8" src="/ueditor/ueditor.config.js"></script>
<script type="text/javascript" charset="utf-8" src="/ueditor/ueditor.all.min.js"> </script>
<script type="text/javascript" charset="utf-8" src="/ueditor/lang/zh-cn/zh-cn.js"></script>

然后插入类似如下模板:

<div>
    <script id="editor" type="text/plain" style="width:1024px;height:500px;"></script>
</div>

<script type="text/javascript">

    //实例化编辑器
    //建议使用工厂方法getEditor创建和引用编辑器实例,如果在某个闭包下引用该编辑器,直接调用UE.getEditor('editor')就能拿到相关的实例
    var ue = UE.getEditor('editor');
</script>

具体的配置和使用,可以参考:http://fex.baidu.com/ueditor/