Let’s encrypt

最近域名主机双双到期了,原来的服务商建议域名迁出,于是转移到了Godaddy,过程很顺利。主机迁到了linode,一个是因为它便宜,另一个是因为想给自己的网站加个SSL证书。
首先是服务器环境Apache, PHP, MaraiDB(MySQL)的配置。 linode 创建主机很简单,点点就好了,然后可以去启动机器,设置SSH访问。

yum update
yum install httpd php php-cli php-mbstring php-pdo php-mysql php-gd php-tidy

启动Apache

systemctl start httpd.service
systemctl enable httpd.service

然后直接访问你的服务器ip,可以看到默认的欢迎界面

ip addr show eth0 | grep inet | awk '{ print $2; }' | sed 's/\/.*$//'
curl 1.139.x.x

CentOS 7目前默认不提供Mysql Server,而是Mariadb。MySQL的命令行仍然可以使用并兼容, PHP仍然可以使用PDO及MySQL扩展访问它。

yum install mariadb-server mariadb
systemctl start mariadb

然后设置管理员密码及安全设置

mysql_secure_installation

开机启动Mariadb服务

systemctl enable mariadb.service

可以在/var/www/html目录下创建一个测试脚本来验证安装情况

<?php
phpinfo();

WordPress则是从原本的数据库全部导出,文件全部打包回来。
在Linode上创建对应的数据库,用户及导入脚本

MariaDB [(none)]> CREATE database courages_wordpress;

MariaDB [(none)]> CREATE USER courages_wp IDENTIFIED BY '*************';

MariaDB [(none)]> grant all privileges on courages_wordpress.* to courages_wp@localhost identified by '*************';

导入数据库

mysql -uroot -p courages_wordpress < /tmp/wordpress.sql

将文件解压并复制到/var/www/html目录

tar -xzvf backup.tar.gz
cp -R backup/public_html/* /var/www/html/*
chown -R apache:apache /var/www/html

更改Apache设置AllowOverride 为all,以便支持WordPress的链接重定向。

vim /etc/httpd/conf/httpd.conf

<Directory "/var/www/html">
    #
    # Possible values for the Options directive are "None", "All",
    # or any combination of:
    #   Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
    #
    # Note that "MultiViews" must be named *explicitly* --- "Options All"
    # doesn't give it to you.
    #
    # The Options directive is both complicated and important.  Please see
    # http://httpd.apache.org/docs/2.4/mod/core.html#options
    # for more information.
    #
    Options Indexes FollowSymLinks

    #
    # AllowOverride controls what directives may be placed in .htaccess files.
    # It can be "All", "None", or any combination of the keywords:
    #   Options FileInfo AuthConfig Limit
    #
    AllowOverride All

    #
    # Controls who can get stuff from this server.
    #
    Require all granted
</Directory>

重启Apache

systemctl restart httpd.service

在linode的DNS manager那里新增一个新的domain,在服务器列表里面选中对应的服务器就可以了,然后就可以看到对应的域名解析信息。
域名转移会要求一个key,从原注册商那里解锁并获得,在Godday输入Key后,它会发邮件与你确认,然后将DNS域名服务器改为linode的域名服务器就好了。

Let’s Encrypt提供免费90天的SSL证书,如果证书到期了就需要再次更新下。如果你有shell权限,它推荐使用Cerbot来安装和更新证书。CentOS 7 + Apache的安装非常简单。首先安装EPEL源,要不然找不到对应的安装包

yum install epel-release
yum install certbot-apache
certbot --authenticator webroot --installer apache

设置一下域名,网站目录及域名重定向

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer apache
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): <[email protected]>
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org

-------------------------------------------------------------------------------
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A

-------------------------------------------------------------------------------
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
-------------------------------------------------------------------------------
(Y)es/(N)o: Y
Starting new HTTPS connection (1): supporters.eff.org
No names were found in your configuration files. Please enter in your domain
name(s) (comma and/or space separated)  (Enter 'c' to cancel): courages.us
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for courages.us
Input the webroot for courages.us: (Enter 'c' to cancel): /var/www/html
Waiting for verification...
Cleaning up challenges

We were unable to find a vhost with a ServerName or Address of courages.us.
Which virtual host would you like to choose?
(note: conf files with multiple vhosts are not yet supported)
-------------------------------------------------------------------------------
1: ssl.conf                       |                       | HTTPS | Enabled
-------------------------------------------------------------------------------
Press 1 [enter] to confirm the selection (press 'c' to cancel): 1
Deploying Certificate for courages.us to VirtualHost /etc/httpd/conf.d/ssl.conf

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
-------------------------------------------------------------------------------
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Created redirect file: le-redirect-courages.us.conf
Rollback checkpoint is empty (no changes made?)

-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://courages.us

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=courages.us
-------------------------------------------------------------------------------

在浏览器访问一下域名即有小绿钥匙。可以在/etc/httpd/conf.d/ssl.conf查看相应的SSL证书配置

<VirtualHost _default_:443>
ServerName courages.us
SSLCertificateFile /etc/letsencrypt/live/courages.us/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/courages.us/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateChainFile /etc/letsencrypt/live/courages.us/chain.pem
</VirtualHost>

由于证书在90天后即将失效,可以加入crontab自动更新

certbot renew --dry-run
crontab -e

0 0,12 * * * python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew 

另外一种更新证书的方法是使用acme.sh,不依赖python,同样支持DNS验证,并且还可以获取其他CA颁发的证书,比如ZeroSSL
安装acme。sh脚本非常简单

➜ ~ curl https://get.acme.sh | sh -s [email protected]
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1032 0 1032 0 0 608 0 --:--:-- 0:00:01 --:--:-- 608
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 216k 100 216k 0 0 104k 0 0:00:02 0:00:02 --:--:-- 104k
[2023年 04月 07日 星期五 22:11:22 CST] Installing from online archive.
[2023年 04月 07日 星期五 22:11:22 CST] Downloading https://github.com/acmesh-official/acme.sh/archive/master.tar.gz
[2023年 04月 07日 星期五 22:11:25 CST] Extracting master.tar.gz
[2023年 04月 07日 星期五 22:11:26 CST] It is recommended to install socat first.
[2023年 04月 07日 星期五 22:11:26 CST] We use socat for standalone server if you use standalone mode.
[2023年 04月 07日 星期五 22:11:26 CST] If you don't use standalone mode, just ignore this warning.
[2023年 04月 07日 星期五 22:11:26 CST] Installing to /root/.acme.sh
[2023年 04月 07日 星期五 22:11:26 CST] Installed to /root/.acme.sh/acme.sh
[2023年 04月 07日 星期五 22:11:26 CST] Installing alias to '/root/.zshrc'
[2023年 04月 07日 星期五 22:11:26 CST] OK, Close and reopen your terminal to start using acme.sh
[2023年 04月 07日 星期五 22:11:26 CST] Installing alias to '/root/.cshrc'
[2023年 04月 07日 星期五 22:11:26 CST] Installing alias to '/root/.tcshrc'
[2023年 04月 07日 星期五 22:11:26 CST] Installing cron job
[2023年 04月 07日 星期五 22:11:26 CST] Good, bash is found, so change the shebang to use bash as preferred.
[2023年 04月 07日 星期五 22:11:28 CST] OK
[2023年 04月 07日 星期五 22:11:28 CST] Install success!

这里采用DNS验证获取证书,设置一下DNS厂商信息,比如DNSPOD,默认获取ZeroSSL证书,可以通过–server letsencrypt切换

➜ ~ export DP_Id="95731"
➜ ~ export DP_Key="***"
➜ ~ acme.sh --issue --dns dns_dp -d example.com -d "*.example.com"
[2023年 04月 07日 星期五 22:14:24 CST] Using CA: https://acme.zerossl.com/v2/DV90
[2023年 04月 07日 星期五 22:14:24 CST] Create account key ok.
[2023年 04月 07日 星期五 22:14:24 CST] No EAB credentials found for ZeroSSL, let's get one
[2023年 04月 07日 星期五 22:14:29 CST] Registering account: https://acme.zerossl.com/v2/DV90
[2023年 04月 07日 星期五 22:14:38 CST] Registered
[2023年 04月 07日 星期五 22:14:38 CST] ACCOUNT_THUMBPRINT='AY5noJZbSullM7fVQOnluxQJmWiiKJHl0vzVsYwwGsg'
[2023年 04月 07日 星期五 22:14:38 CST] Creating domain key
[2023年 04月 07日 星期五 22:14:38 CST] The domain key is here: /root/.acme.sh/example.com_ecc/example.com.key
[2023年 04月 07日 星期五 22:14:38 CST] Multi domain='DNS:example.com
[2023年 04月 07日 星期五 22:14:39 CST] Getting domain auth token for each domain
[2023年 04月 07日 星期五 22:14:56 CST] Getting webroot for domain='example.com'
[2023年 04月 07日 星期五 22:14:56 CST] Getting webroot for domain='*.example.com'
[2023年 04月 07日 星期五 22:14:56 CST] Adding txt value: q-pQccpOPbYbC4h_veQTSvj2OgUpgg9kBUFpms9fa9k for domain: _acme-challenge.example.com
[2023年 04月 07日 星期五 22:14:57 CST] Adding record
[2023年 04月 07日 星期五 22:14:57 CST] The txt record is added: Success.
[2023年 04月 07日 星期五 22:15:01 CST] Let's check each DNS record now. Sleep 20 seconds first.
[2023年 04月 07日 星期五 22:15:23 CST] You can use '--dnssleep' to disable public dns checks.
[2023年 04月 07日 星期五 22:15:23 CST] See: https://github.com/acmesh-official/acme.sh/wiki/dnscheck
[2023年 04月 07日 星期五 22:15:23 CST] Checking example.com for _acme-challenge.example.com
[2023年 04月 07日 星期五 22:15:23 CST] Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: 35
[2023年 04月 07日 星期五 22:15:27 CST] Domain example.com '_acme-challenge.example.com' success.
[2023年 04月 07日 星期五 22:15:34 CST] All success, let's return
[2023年 04月 07日 星期五 22:15:34 CST] Verifying: example.com
[2023年 04月 07日 星期五 22:15:46 CST] Processing, The CA is processing your order, please just wait. (1/30)
[2023年 04月 07日 星期五 22:15:51 CST] Success
[2023年 04月 07日 星期五 22:15:51 CST] Verifying: *.example.com
[2023年 04月 07日 星期五 22:15:55 CST] Processing, The CA is processing your order, please just wait. (1/30)
[2023年 04月 07日 星期五 22:16:28 CST] Success
[2023年 04月 07日 星期五 22:16:28 CST] Removing DNS records.
[2023年 04月 07日 星期五 22:16:28 CST] Removing txt: q-pQccpOPbYbC4h_veQTSvj2OgUpgg9kBUFpms9fa9k for domain: _acme-challenge.example.com
[2023年 04月 07日 星期五 22:16:30 CST] Removed: Success
[2023年 04月 07日 星期五 22:16:34 CST] Verify finished, start to sign.
[2023年 04月 07日 星期五 22:16:35 CST] Lets finalize the order.
[2023年 04月 07日 星期五 22:16:40 CST] Order status is processing, lets sleep and retry.
[2023年 04月 07日 星期五 22:16:40 CST] Retry after: 15
[2023年 04月 07日 星期五 22:17:00 CST] Downloading cert.
[2023年 04月 07日 星期五 22:17:00 CST] Le_LinkCert='https://acme.zerossl.com/v2/DV90/cert/pcAU9PLEoObR7xYPTb9x7w'
[2023年 04月 07日 星期五 22:17:03 CST] Cert success.
-----BEGIN CERTIFICATE-----
MIIELzCCA7agAwIBAgIRAOO4Gyu...
QRwR2SSrAlEIeJXua7aYyyEEJQ==
-----END CERTIFICATE-----
[2023年 04月 07日 星期五 22:17:03 CST] Your cert is in: /root/.acme.sh/example.com_ecc/example.com.cer
[2023年 04月 07日 星期五 22:17:03 CST] Your cert key is in: /root/.acme.sh/example.com_ecc/example.com.key
[2023年 04月 07日 星期五 22:17:03 CST] The intermediate CA cert is in: /root/.acme.sh/example.com_ecc/ca.cer
[2023年 04月 07日 星期五 22:17:03 CST] And the full chain certs is there: /root/.acme.sh/example.com_ecc/fullchain.cer

最后安装证书

➜  ~ acme.sh --install-cert -d example.com -d "*.example.com"--key-file /etc/letsencrypt/live/example.com/privkey.pem --fullchain-file /etc/letsencrypt/live/example.com/chain.pem --reloadcmd "systemctl restart nginx"
[2023年 04月 07日 星期五 23:00:13 CST] The domain 'example.com' seems to have a ECC cert already, lets use ecc cert.
[2023年 04月 07日 星期五 23:00:13 CST] Installing key to: /etc/letsencrypt/live/example.com/privkey.pem
[2023年 04月 07日 星期五 23:00:13 CST] Installing full chain to: /etc/letsencrypt/live/example.com/chain.pem
[2023年 04月 07日 星期五 23:00:13 CST] Run reload cmd: systemctl restart nginx
[2023年 04月 07日 星期五 23:00:13 CST] Reload success

以后就可以自动更新了

➜  ~  crontab -l  | grep acme 
26 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

最近登录后台发现WordPress提示升级到PHP 7.3,按照它的指示

在CentOS 7上升级PHP5.4 到PHP 7.3很简单:
首先安装Remi和EPEL仓库

yum install wget
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7.rpm 
rpm -Uvh epel-release-latest-7.noarch.rpm

yum install yum-utils

启用remi-php73的源,yum update升级会自动升级PHP及扩展

[root@li846-239 ~]# yum-config-manager --enable remi-php73
[root@li846-239 ~]# yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.linode.com
 * epel: kartolo.sby.datautama.net.id
 * extras: mirrors.linode.com
 * remi-php73: mirror.xeonbd.com
 * remi-safe: mirror.xeonbd.com
 * updates: mirrors.linode.com
repo id                                                                                                   repo name                                                   status
base/7/x86_64                                                                                             CentOS-7 - Base                                              10,019
epel/x86_64                                                                                               Extra Packages for Enterprise Linux 7 - x86_64               13,051
extras/7/x86_64                                                                                           CentOS-7 - Extras                                               385
remi-php73                                                                                                Remi's PHP 7.3 RPM repository for Enterprise Linux 7 - x86_64   305
remi-safe                                                                                                 Safe Remi's RPM repository for Enterprise Linux 7 - x86_64    3,188
updates/7/x86_64                                                                                          CentOS-7 - Updates                                            1,511
repolist: 28,825


yum update -y

检查PHP版本,重启Apache

[root@li846-239 ~]# php -v
PHP 7.3.4 (cli) (built: Apr  2 2019 13:48:50) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.4, Copyright (c) 1998-2018 Zend Technologies
[root@li846-239 ~]# systemctl restart httpd

也可以禁用对应PHP版本的源,选择性升级PHP到对应版本

yum-config-manager --disable remi-php72

顺便升级下php-mcrypt和ZipArchive

yum install php-mcrypt
yum install php-pecl-zip

参考链接:
How To Install Linux, Apache, MySQL, PHP (LAMP) stack On CentOS 7
How to enable EPEL repository?
How to Secure Your Server
Introduction to FirewallD on CentOS
How to Upgrade PHP 5.6 to PHP 7.2 on CentOS VestaCP

Python使用Kerberos认证查询Impala

最近做QoS报告,数据来源于Impala,客户端认证采用的是Kerberos。
Impala是Cloudera公司开发并开源的一款基于HDFS/Hbase的MPP SQL引擎,它提供SQL语义,能够查询存储在Hadoop的HDFS和HBase中的PB级大数据。Kerberous本身是一个网络认证授权协议,借由中心服务器认证,对通信双方的客户端/服务端进行授权而不需要传递双方的密码。Kerberos的认证流程比较有意思,分为三个阶段

  • 客户端认证
    • 1 客户端发送自己用户名
    • 2 认证服务器返回使用客户端密钥加密的Client/TGS会话密钥和使用票据授权服务器密钥加密的TGT, 包括sessions key,用户信息及有效期
    • 3 客户端使用自己的密钥解密出Client/TGS会话密钥
  • 服务授权
    • 1 客户端发送两条消息:接收到的TGT和所请求的服务ID;使用Client/TGS会话密钥加密的用户ID和时间戳
    • 2 票据授权服务器使用自己的密钥解密TGT得到客户端的Client/TGS会话密钥,然后使用它解密出用户ID并进行认证。返回使用所请求服务端密钥加密的client-server票据和使用Client/TGS会话密钥加密的Client/Server会话密钥
    • 3 客户端使用Client/TGS会话密钥(Client/TGS Session Key)解密出Client/Server会话密钥
  • 服务请求
    • 1 客户端发送两条消息:使用所请求服务端密钥加密的client-server票据及使用Client/Server会话密钥加密的用户ID和时间戳
    • 2 服务端使用自己的密钥解密client-server票据从而得到Client/Server会话密钥,使用该密钥解密获得用户信息并认证。返回使用Client/Server会话密钥的新时间戳
    • 3 客户端使用Client/Server会话密钥解密该消息,认证结束并请求服务
    • 4 服务端提供服务

在CentOS上安装Kerberos

yum install krb5-devel pam_krb5 krb5-libs krb5-workstation

编辑配置

vim /etc/krb5.conf

配置KDC,认证服务器

[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
default_realm = EXAMPLE.COM
dns_lookup_realm = false
dns_lookup_kdc = true
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
default_tkt_enctypes = rc4-hmac
default_tgs_enctypes = rc4-hmac
permitted_enctypes = rc4-hmac
[realms]
EXAMPLE.COM = {
default_domain = example.com
kdc = kdc01.example.com
kdc = kdc02.example.com
admin_server = adc01.example.com
admin_server = adc02.example.com
}
[domain_realm]
.example.com = EXAMPLE.COM
example.com = EXAMPLE.COM

测试一下

[root@localhost rc]# kinit [email protected]
Password for [email protected]:

注意这个配置文件每行前面的空格被删掉,是因为在VirtualBox里面每行开头有莫名其妙的乱码,Linux下并不可见,在EditPlus下面才发现,否则会乱报错

kinit: Improper format of Kerberos configuration file while initializing Kerberos 5 library
kinit: Cannot find KDC for realm "EXAMPLE.COM" while getting initial credentials

查看一下认证的ticket

[root@localhost vagrant]# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: [email protected]

Valid starting       Expires              Service principal
09/21/2017 08:30:50  09/21/2017 18:30:50  krbtgt/[email protected]
        renew until 09/28/2017 08:30:42

这个ticket在28号就会过期了,到时候又要输入密码,这样也不便于自动化程序使用。可以使用ktutil创建keytab文件

$ ktutil
ktutil:  addent -password -p [email protected] -k 1 -e RC4-HMAC
Password for [email protected]:
ktutil:  wkt abc.xyz.keytab
ktutil:  q
$ ls
abc.xyz.keytab

测试一下

$ kinit -kt abc.xyz.keytab [email protected]
$ klist -k abc.xyz.keytab
Keytab name: FILE:abc.xyz.keytab
KVNO Principal
---- --------------------------------------------------------------------------
  1 [email protected]

之后便可以使用kinit自动更新ticket了。注意,如果更换了密码,需要重新生成新的keytab。
另外,相同用户生成的授权ticket在任意一台机器上都是相同的, kinit时会自动同步回来的。
公司的大数据平台使用Hue来提供基于web界面的查询,Impala也支持使用ODBC方式查询。在Python里使用的是impyla来查询,首先安装sasl的依赖

yum install libgsasl-devel cyrus-sasl-devel cyrus-sasl-gssapi
pip install impyla thrift_sasl

测试脚本

from impala.dbapi import connect
conn = connect(host="impalad.example.com", port=21050, auth_mechanism='GSSAPI', kerberos_service_name='impala', database='acme')
cur =  conn.cursor()
cur.execute(r'SELECT * FROM acme WHERE dt="2017-09-12" LIMIT 5')
print(cur.fetchall())

运行下

python test.py

如下报错,则是服务器不能连接,检查一下网络,DNS/hosts及VPN

thriftpy.transport.TTransportException: TTransportException(type=1, message="Could not connect to ('impalad.example.com', 21050)")

如下报错,CentOS则是需要cyrus-sasl-gssapi模块

thriftpy.transport.TTransportException: TTransportException(type=1, message="Could not start SASL: b'Error in sasl_client_start (-4) SASL(-4): no mechanism available: No worthy mechs found'")

参考链接:
Impala:新一代开源大数据分析引擎
大数据时代快速SQL引擎-Impala
CDH 5.2中Impala认证集成LDAP和Kerberos
Kerberos
Configuring Kerberos Authentication for Windows
Speaking Kerberos with KNIME Big Data Extensions

使用Supervisor 管理监控进程

Supervisor是用Python写的一款应用程监控管理工具,能够启动,停止,重启死进程,提供web管理界面,XML-RPC接口及事件监听。通常我们写了一些脚本都不会带有daemon功能,而是加&或者nohub,screen什么的丢到后台去运行,同时使用corntab定时检测脚本是否存活,以便重新运行脚本。使用Supervisor可以将这些脚本,程序转为守护进程,自动重启它们;还可以监控机器的进程运行状况,输出警报等。
Supervisor只能运行于Python 2.x的环境,但子进程可以为其他任意程序,比如Python 3,PHP等。这里使用pip来安装

$ wget https://bootstrap.pypa.io/get-pip.py
$ python -V
$ sudo python get-pip.py
$ sudo pip install supervisor

生成配置文件及日志目录

$ sudo echo_supervisord_conf > /etc/supervisord.conf
$ mkdir /var/log/supervisor
$ chmod 655 /var/log/supervisor

启动supervisord

$ sudo supervisord -c /etc/supervisord.conf
$ supervisorctl
$ Server requires authentication
$ Username:user
$ Password:

$ supervisor> status
$ supervisor> help

default commands (type help <topic>):
=====================================
add    exit      open  reload  restart   start   tail
avail  fg        pid   remove  shutdown  status  update
clear  maintail  quit  reread  signal    stop    version
$ supervisor> quit

这里没有任何进程。以下为常用命令:

  • supervisorctl stop program 停止某个进程
  • supervisorctl start program 启动某个进程
  • supervisorctl restart program 重启某个进程
  • supervisorctl stop group 重启属于group分组的所有进程(start,restart同理)
  • supervisorctl stop all 停止全部进程,注:start、restart、stop都不会载入最新的配置文件
  • supervisorctl reload 载入最新配置文件,停止原有进程并按新配置启动进程
  • supervisorctl update 根据最新配置文件,启动新配置或有改动的进程,没有改动的进程不受影响

编辑supervisord.conf启用web界面,账号密码为web及supervisorctl共用,必须更改

$ sudo vim /etc/supervisord.conf
#取消以下行的注释
[inet_http_server]         ; inet (TCP) server disabled by default
port=*:9002           ; ip_address:port specifier, *:port for all iface
username=user              ; default is no username (open server)
password=123               ; default is no password (open server)

#添加新应用qrd
[program:qrd]
command = /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
directory = /home/qrd/qrd/
user = root
autostart = true
autorestart = true
startsecs = 5
startretries = 3
stdout_logfile = /var/log/supervisor/qrd.log
stderr_logfile = /var/log/supervisor/qrd.log

重启使HTTP配置生效

ps aux | grep supervisord 
kill 
supervisord -c /etc/supervisord.conf

关于supervisord.conf配置项,命令释义可以参考这里

  • command 为要运行的脚本或程序
  • directory 为脚本或程序运行时的工作目录
  • user 为脚本或程序运行时的用户
  • autostart 随supervisord启动
  • startsecs 启动后等待时间,过了这个时间没起来就重新启动
  • startretries 启动失败后重试的次数
  • stdout_logfile,stderr_logfile 为输出日志

重新加载所有应用

$ supervisorctl
Server requires authentication
Username:user
Password:

supervisor> reload
Really restart the remote supervisord process y/N? y
Restarted supervisord
supervisor> status
qrd                              RUNNING   pid 3861, uptime 0:00:22

可以看到定义的qrd程序已经起来了。如果qrd程序意外退出了,那么supervisord将会重启它。如果杀掉了supervisord,那么qrd对应的进程也将被杀死。也可以去web界面查看http://127.0.0.1:9002/

可以通过web页面来启动,停止进程,查看日志等。
再增加下Celery的Worker,Beat配置

; ==================================
;  celery worker supervisor example
; ==================================

[program:qrdworker]
; Set full path to celery program if using virtualenv
command=/usr/bin/celery worker -A qrd --loglevel=INFO

; Alternatively,
;command=celery --app=your_app.celery:app worker --loglevel=INFO -n worker.%%h
; Or run a script
;command=celery.sh

directory=/home/qrd/qrd/
user=nobody
numprocs=1
stdout_logfile=/var/log/supervisor/qrdworker.log
stderr_logfile=/var/log/supervisor/qrdworker.log
autostart=true
autorestart=true
startsecs=10

; Need to wait for currently executing tasks to finish at shutdown.
; Increase this if you have very long running tasks.
stopwaitsecs = 600

; Causes supervisor to send the termination signal (SIGTERM) to the whole process group.
stopasgroup=true

; Set Celery priority higher than default (999)
; so, if rabbitmq is supervised, it will start first.
priority=1000

; ================================
;  celery beat supervisor example
; ================================

[program:qrdbeat]
; Set full path to celery program if using virtualenv
command=/usr/bin/celery -A qrd beat -l info -s /tmp/celerybeat-schedule

; remove the -A myapp argument if you aren't using an app instance

directory=/home/qrd/qrd/
user=nobody
numprocs=1
stdout_logfile=/var/log/supervisor/beat.log
stderr_logfile=/var/log/supervisor/beat.log
autostart=true
autorestart=true
startsecs=10

; Causes supervisor to send the termination signal (SIGTERM) to the whole process group.
stopasgroup=true

; if rabbitmq is supervised, set its priority higher
; so it starts first
priority=999

然后启用它们

supervisor> update
qrdbeat: added process group
supervisor> status
qrd                              RUNNING   pid 4468, uptime 0:03:49
qrdbeat                          BACKOFF   Exited too quickly (process log may have details)
qrdworker                        RUNNING   pid 4469, uptime 0:03:49

查看下进程

$ sudo ps aux | grep python
root      1038  0.0  3.1 562392 15720 ?        Ssl  01:49   0:01 /usr/bin/python -Es /usr/sbin/tuned -l -P
root      3992  0.1  3.0 222124 15224 ?        Ss   03:49   0:00 /bin/python /bin/supervisord -c /etc/supervisord.conf
root      3993  0.3  3.8 211868 19296 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
root      3996  0.3  6.9 264048 34780 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
root      3998  0.3  6.9 264056 34784 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
root      3999  0.4  6.9 264024 34784 ?        S    03:49   0:00 /usr/local/python3/bin/python3.6 /usr/bin/gunicorn --access-logfile - --workers 3 --bind 127.0.0.1:8000 qrd.wsgi
root      4014  0.0  0.1 112660   976 pts/0    R+   03:50   0:00 grep --color=auto python

除了program标签,Supervisor也支持group标签,用来启动一系列的program,比如先启动数据库,再启动web服务器。
Supervisor也可以用来管理PHP进程,比如使用 Supervisor 管理 Laravel 队列进程
Docker官方推荐一个docker容器只运行一个服务,如果你想启动多个脚本或程序,可以使用Supervisor会更简单点
如果需要Supervisor开启启动,可以使用GitHub上的脚本和配置。CentOS 7上可以配置为service,由systemctl来管理:

$ sudo vim /etc/systemd/system/supervisord.service

内容如下:

# supervisord service for systemd (CentOS 7.0+)
# by ET-CS (https://github.com/ET-CS)
[Unit]
Description=Supervisor daemon

[Service]
Type=forking
ExecStart=/usr/bin/supervisord
ExecStop=/usr/bin/supervisorctl $OPTIONS shutdown
ExecReload=/usr/bin/supervisorctl $OPTIONS reload
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

然后启用

$ sudo systemctl enable supervisord.service
Created symlink from /etc/systemd/system/multi-user.target.wants/supervisord.service to /etc/systemd/system/supervisord.service.
$ sudo systemctl start supervisord.service
$ sudo ps aux | grep python

虽然supervisorctl及web界面都只能管理本机,但是Supervisor提供了XML-RPC接口,可以获取进程运行信息,因此诞生了许多监控平台,以便监控服务器集群的进程的运行状况。
Supervisor还提供了event listener配置,在这里Supervisor将会通知脚本其他进程的运行状态变更的事件,可以用来发送警报监控等。

参考链接:
使用Supervisor3.2.1基于Mac10.10.3对系统进程进行管理
supervisor的配置浅析
Python 进程管理工具 Supervisor 使用教程
Supervisor进程监护使用指南
supervisord 的 XML-RPC API 使用说明
Supervisor Event Listener
Dockerizing Nginx and SSH using Supervisord

PHP MongoDB Replica Set应用

MongoDB是个面向文档管理的NoSQL数据库,能够直接存取JSON数据,支持海量数据。公司内部的一个单点登录系统使用MongoDB来存储用户的session,以便在不同应用,服务器之间共享登录信息,解决了服务器切换用户状态丢失(被登出)的问题。单点登录系统前端采用PHP,与LDAP交互认证用户信息,提供登录界面,API等。MongoDB则采用Replica Set模式以便支持高可用。
在CentOS 6.5上安装MongoDB,首先添加源仓库

$ suod vim /etc/yum.repos.d/mongodb.repo
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=0

安装,启动,连接MongoDB

$ sudo yum --enablerepo=mongodb install mongodb-org
$ sudo /sbin/chkconfig --levels 235 mongod on
$ sudo mongod --port 27017 --dbpath /data/db1
$ mongo --port 27017

创建用户SSOReplUser

> use admin 
> db.createUser( { user: "SSOReplUser", pwd: "<password>", roles: [ { role: "root", db: "admin" } ] });

创建集群要用到的key

$ sudo openssl rand -base64 741 > /home/sso/mongodb-keyfile 
$ sudo chmod 600 /home/sso/mongodb-keyfile

编辑MongoDB配置文件

$ sudo vim /etc/mongod.conf
# Replication Options 
# in replicated mongo databases, specify the replica set name here 
replSet=SSOReplSet 
# maximum size in megabytes for replication operation log 
#oplogSize=1024 
# path to a key file storing authentication info for connections # between replica set members keyFile=/home/sso/mongodb-keyfile

重启mongod服务

$ sudo /etc/init.d/mongod stop   
$ sudo /usr/bin/mongod -f /etc/mongod.conf

使用SSOReplUser登录

> use admin 
> db.auth("SSOReplUser", "<password>");

初始化集群

> rs.initiate()
> rs.conf()   
{
  "_id": "SSOReplSet",
  "version": 1,
  "members": [
    {
      "_id": 1,
      "host": "192.168.33.10:27017"
    }
  ]
}

在其他机器上按照上面步骤安装MongoDB,更改配置,复制对应的mongodb-keyfile并启动。
回到192.168.33.10并添加机器

SSOReplSet:PRIMARY> rs.add("192.168.33.11:27017") 
SSOReplSet:PRIMARY> rs.add("192.168.33.12:27017") 
SSOReplSet:PRIMARY> rs.add("192.168.33.13:27017")
#SSOReplSet:PRIMARY> rs.remove("192.168.33.13:27017")

在从机上认证查看

SSOReplSet:SECONDARY> use admin 
SSOReplSet:SECONDARY> db.auth("SSOReplUser", "<password>");   
SSOReplSet:SECONDARY> rs.status()

MongoDB的Replica set模式最少要求3台机器,以便在PRIMARY机器故障时,能够自我选举出新的PRIMARY,但需要n/2+1的机器投票同意。例如刚才配置了4台机器,那么需要4/2+1=3台机器投票,能够承受一台机器故障。如果是集群有5台机器,则能够承受2台机器故障。因此再配置一台不存储数据的Arbiter机器比较合理。
按照上面的步骤安装,复制mongodb-keyfile文件,但需要更改mongod.conf

$sudo vim /etc/mongod.conf
nojournal=true

然后启动mongod服务并在PRIMARY机器上将这台机器加入机器

SSOReplSet:PRIMARY> use admin   
SSOReplSet:PRIMARY> db.auth("SSOReplUser", "<password>");   
SSOReplSet:PRIMARY> rs.addArb("<ip of arbiter>");

PHP的mongo扩展安装比较简单,下载对应版本,启用即可。然而在应用过程中发现登录有点慢,启用看PHP mongo扩展profiling功能,查看PHP日志

<?php
\MongoLog::setLevel(\MongoLog::ALL);
\MongoLog::setModule(\MongoLog::ALL);
try{
    echo  microtime(true) . PHP_EOL;
    echo "aaa01(primary)" . PHP_EOL;
    $m = new \MongoClient("mongodb://admin:[email protected]:27017/?replicaSet=SSOReplSet ");
    echo  microtime(true) . PHP_EOL;
    echo "aaa01(primay), bbb01(secondary),ccc01(secondary),ddd01(secondary)" . PHP_EOL;
    $m = new \MongoClient("mongodb://admin:[email protected]:27017,192.168.33.11:27017,192.168.33.12:27017,192.168.33.13:27017/?replicaSet=SSOReplSet ");
    echo  microtime(true) . PHP_EOL;
} catch (\MongoConnectionException $e) {
    var_dump($e);
}

发现PHP连接MongoDB集群是这样子的

即:

  • 1)在连接里面配置了几个MongoDB连接服务器,PHP每个都会创建连接去查询
  • 2)从每台服务器上查询出整个集群的服务器列表,再分别ping和连接这些服务器,如果连接不存在或不匹配则创建,无效的则销毁
  • 3)汇总所有服务器返回集群列表
  • 4)选择离自己最近的服务器并使用该与该服务器的连接
  • MongoDB 的写操作默认在Primary节点上操作完成即返回成功;读操作默认是也是从Primary上读取,所以需要去查询服务器。由于SSO应用部署在多个数据中心,网络抖动会造成较大影响,跨数据中心的查询并不会很快,如果每次都去连接并查询整个列表是比较耗时。另外如果在php里面配置的是IP而MongoDB Replica Set里面配置的是域名,则连接名会出现不匹配,而创建新的连接并销毁旧连接,也耗时。如果配置的是域名,则需要DNS解析。由于每台PHP服务器均已配置HA检测,最终每个应用只配置了一台服务器,并统一配置MongoDB集群为IP。而连接最快的是在Primary机器上,可以根据本机是否Primary来做HA:

    #!/usr/bin/env bash
    count=`ps -fe |grep "mongod" | grep -v "grep" | wc -l`
    FILE="/home/scripts-bits/master.conf"
    SERVER=`hostname`
    
    if [ $count -lt 1 ]; then
        rm -f $FILE
    else
        PRIMAY=`/usr/bin/mongo ${SERVER}:27017 --quiet --eval 'printjson(db.isMaster().ismaster);'`
        if [ "$PRIMAY" == "true" ]; then
        	if [ ! -f "$FILE" ]; then
        		touch "$FILE"
        	fi
        	REMOVE=`/usr/bin/mongo ${SERVER}:27017/admin --quiet /home/scripts-bits/mongo_status.js`
        fi
    fi
    

    删除故障节点(not reachable/healthy)的脚本mongo_status.js:

    db.auth('admin','<password>');
    conf=rs.status();
    members=conf["members"];
    for(i in members){
    	if(members[i]["state"] == 8){
    		rs.remove(members[i]["name"]);
    	}
    }
    

    这中间也出现过因为网络/机器故障,导致PHP等待连接超时的情况的,将该机器从集群中移除即可。然而当服务器是启动的(可以到达)情况下,MongoDB故障,则不会超时。可以更改connectTimeoutMS,以便减少等待。
    MongoDB的日志默认都是记录在/var/log/mongodb/下面并且会越来越大。创建Python脚本来定时清除它:

    #!/bin/env python
    import commands
    import datetime,time
    
    def rotate_log(path, expire = 30):
        str_now = time.strftime("%Y-%m-%d")
        dat_now = time.strptime(str_now, "%Y-%m-%d")
        array_dat_now = datetime.datetime(dat_now[0], dat_now[1], dat_now[2])
        lns = commands.getoutput("/bin/ls --full-time %s|awk '{print $6, $9}'" % path)
        for ln in lns.split('\n'):
            ws = ln.split()
            if len(ws) != 2:
                continue
            ws1 = time.strptime(ws[0], "%Y-%m-%d")
            ws2 = datetime.datetime(ws1[0], ws1[1], ws1[2])
            if (array_dat_now - ws2).days > expire:
                v_del = commands.getoutput("/bin/rm -rf %s/%s" % (path, ws[1]))
    
    
    def rotate_mongo():
        # get mongo pid
        mongo_pid = commands.getoutput("/sbin/pidof mongod")
        #print mongo_pid
        # send Sig to mongo
        if mongo_pid != '':
            cmd = "/bin/kill -USR1 %s" % (mongo_pid)
            # print cmd
            mongo_rotate = commands.getoutput(cmd)
        else:
            print "mongod is not running..."
    
    if __name__ == "__main__":
        log_path = "/var/log/mongodb/"
        expire = 30
        rotate_mongo()
        rotate_log(log_path, expire)
    
    

    加入到crontab里面去执行

    10 1 * * * /usr/bin/python /home/sso/mongo_rotate.py > /dev/null 2>&1
    

    参考连接
    mongodb与mysql相比的优缺点
    PHP MongoDB 复制集合
    MongoDB Replication
    MongoDB Enable Auth
    MongoDB Rotate Log Files
    Blocking connect() leads to cumulative timeouts for multiple inaccessible servers
    How Raft consensus algorithm will make replication even better in MongoDB 3.2
    Write Concern for Replica Sets
    MongoDB Read Preference

    Vagrant CentOS 共享目录挂载问题解决

    一直以来都是使用Vagrant与VirtualBox运行CentOS系统来搭建环境,然而有一天突然出现Windows下面的目录无法映射进去了,报错:

    D:\project\vagrant\centos64php56>vagrant up
    Bringing machine 'default' up with 'virtualbox' provider...
    ==> default: Clearing any previously set forwarded ports...
    ==> default: Clearing any previously set network interfaces...
    ==> default: Preparing network interfaces based on configuration...
      default: Adapter 1: nat
      default: Adapter 2: hostonly
    ==> default: Forwarding ports...
      default: 80 (guest) => 8080 (host) (adapter 1)
      default: 22 (guest) => 2222 (host) (adapter 1)
    ==> default: Booting VM...
    ==> default: Waiting for machine to boot. This may take a few minutes...
      default: SSH address: 127.0.0.1:2222
      default: SSH username: vagrant
      default: SSH auth method: private key
      default: Warning: Connection reset. Retrying...
      default: Warning: Connection aborted. Retrying...
      default: Warning: Connection reset. Retrying...
      default: Warning: Remote connection disconnect. Retrying...
    ==> default: Machine booted and ready!
    [default] GuestAdditions versions on your host (5.1.26) and guest (4.3.6) do not matc
    Loaded plugins: fastestmirror
    Setting up Install Process
    Loading mirror speeds from cached hostfile
     * base: mirrors.btte.net
     * epel: repo.fedoralinux.ir
     * extras: mirrors.btte.net
     * updates: mirrors.btte.net
    No package kernel-devel-2.6.32-358.23.2.el6.x86_64 available.
    Package gcc-4.4.7-18.el6.x86_64 already installed and latest version
    Package binutils-2.20.51.0.2-5.47.el6_9.1.x86_64 already installed and latest version
    Package 1:make-3.81-23.el6.x86_64 already installed and latest version
    Package 4:perl-5.10.1-144.el6.x86_64 already installed and latest version
    Package bzip2-1.0.5-7.el6_0.x86_64 already installed and latest version
    Nothing to do
    Copy iso file C:\Program Files\Oracle\VirtualBox\VBoxGuestAdditions.iso into the box
    Installing Virtualbox Guest Additions 5.1.26 - guest version is 4.3.6
    Verifying archive integrity... All good.
    Uncompressing VirtualBox 5.1.26 Guest Additions for Linux...........
    VirtualBox Guest Additions installer
    Removing installed version 5.1.26 of VirtualBox Guest Additions...
    vboxadd.sh: Stopping VirtualBox Additions.
    Copying additional installer modules ...
    Installing additional modules ...
    vboxadd.sh: Starting the VirtualBox Guest Additions.
    Failed to set up service vboxadd, please check the log file
    /var/log/VBoxGuestAdditions.log for details.
    An error occurred during installation of VirtualBox Guest Additions 5.1.26. Some func
    In most cases it is OK that the "Window System drivers" installation failed.
    vboxadd.sh: Starting the VirtualBox Guest Additions.
    vboxadd.sh: failed: Look at /var/log/vboxadd-install.log to find out what went wrong.
    vboxadd.sh: failed: modprobe vboxguest failed.
    ==> default: Checking for guest additions in VM...
      default: The guest additions on this VM do not match the installed version of
      default: VirtualBox! In most cases this is fine, but in rare cases it can
      default: prevent things such as shared folders from working properly. If you see
      default: shared folder errors, please make sure the guest additions within the
      default: virtual machine match the version of VirtualBox you have installed on
      default: your host and reload your VM.
      default:
      default: Guest Additions Version: 4.3.6
      default: VirtualBox Version: 5.1
    ==> default: Configuring and enabling network interfaces...
      default: SSH address: 127.0.0.1:2222
      default: SSH username: vagrant
      default: SSH auth method: private key
    ==> default: Mounting shared folders...
      default: /home/rc => D:/project/vagrant/centos64php56
    Vagrant was unable to mount VirtualBox shared folders. This is usually
    because the filesystem "vboxsf" is not available. This filesystem is
    made available via the VirtualBox Guest Additions and kernel module.
    Please verify that these guest additions are properly installed in the
    guest. This is not a bug in Vagrant and is usually caused by a faulty
    Vagrant box. For context, the command attempted was:
    
    mount -t vboxsf -o uid=500,gid=500 home_rc_ /home/rc
    
    The error output from the command was:
    
    /sbin/mount.vboxsf: mounting failed with the error: No such device
    

    这里提示虚拟机里CentOS的VBoxGuestAdditions与VirtualBox的版本匹配,需要升级:

    [default] GuestAdditions versions on your host (5.1.26) and guest (4.3.6) do not matc
    

    应该是升级了VirtualBox导致的。然而自动安装新插件失败:

    vboxadd.sh: Starting the VirtualBox Guest Additions.
    Failed to set up service vboxadd, please check the log file
    /var/log/VBoxGuestAdditions.log for details.
    An error occurred during installation of VirtualBox Guest Additions 5.1.26. Some func
    In most cases it is OK that the "Window System drivers" installation failed.
    vboxadd.sh: Starting the VirtualBox Guest Additions.
    vboxadd.sh: failed: Look at /var/log/vboxadd-install.log to find out what went wrong.
    vboxadd.sh: failed: modprobe vboxguest failed.
    

    导致共享目录无法映射。但是虚拟机仍然是启动成功的,可以ssh进去,或者使用sftp挂载。
    Google了下有说是VirtualBox bug的,也有说是Windows问题的,各种折腾不能解决。升级插件也无效:vagrant plugin install vagrant-vbguest。重新安装VirtualBox和Vagrant,并不会影响现有虚拟机及网络配置,但不能解决问题。
    直到看到这篇文章,决定从CentOS入手解决。
    启动虚拟机后,使用sftp上传VBoxGuestAdditions.iso,ssh进入手动安装:

    $ sudo
    $ mount VBoxGuestAdditions.iso -o loop /mnt
    $ cd /mnt
    $ sh VBoxLinuxAdditions.run
    

    安装失败,查看日志:

    Building the main Guest Additions module                   [FAILED]
    (Look at /var/log/vboxadd-install.log to find out what went wrong)
    

    vboxadd-install.log日志:

    /tmp/vbox.0/Makefile.include.header:97: *** Error: unable to find the sources of your current Linux kernel. Specify KERN_DIR=<directory> and run Make again.  Stop.
    

    参照这篇文章,查看kernel版本

    $ rpm -qa kernel\* | sort
    kernel-2.6.32-358.23.2.el6.x86_64
    kernel-devel-2.6.32-696.10.2.el6.x86_64
    kernel-firmware-2.6.32-358.10.2.el6.noarch
    kernel-headers-2.6.32-696.10.2.el6.x86_64
    
    $ uname -r
    2.6.32-358.10.2.el6.x86_64
    

    其实一开始是更多版本不匹配的,尝试更新kernel:

    yum update
    yum install kernel-headers kernel-devel
    

    结果部分更新失败:

    Warning: No matches found for: kernel-devel
    No Matches found
    

    参照这里解除版本锁定,设置enabled = 0:

    $ vim /etc/yum/pluginconf.d/versionlock.conf
    

    再次运行升级kernel就可以了。安装成功后,重启后,再次运行sh VBoxLinuxAdditions.run 就可以了。事实上Vagrant启动时就会自动安装VBoxGuestAdditions:

    [default] GuestAdditions 5.1.26 running --- OK.
    

    Vagrant升级到后发现vagrant up初始化下载box卡住了,那是你的vagrant版本太高与对应的powershell版本对应不上,可以下载最新的powershell安装即可。
    如果box下载很慢,可以参照这里的方法自我映射取得url单独下载:

    https://app.vagrantup.com/box-cutter/boxes/centos73
    ==> https://atlas.hashicorp.com/box-cutter/boxes/centos73/versions/2.0.21/providers/virtualbox.box
    

    参考链接:
    Guest Additions Version error on VirtualBox5
    VirtualBox下挂载共享文件目录问题处理
    Resolving GuestAdditions version mismatch in vagrant/homestead vm (failed to mount shared folders / modprobe vboxsf failed)
    Problem installing virtualBox guest additions
    Warning: No matches found for: kernel-devel
    Vagrant up hangs forever on Windows 7, Vagrant 1.9.7, VirtualBox 5.1.22.r11512