Jenkins Publish over SSH权限配置小记

继上次的Jenkins使用简介,现已将Jenkins用在实际项目中,这里介绍下Publish over SSH的使用和服务器用户权限配置。

Publish over SSH插件主要功能是将构建好的应用发布到远程机。用户登录可以使用key或者密码方式,支持添加多个server。不过有一点特殊,只能添加一套密码或key。

猜测作者的思路是想用统一账号用于构建操作,基于这种情况我们为各个服务器创建统一账号,以下基于key方式。

创建key

选一个服务器创建key,步骤如下:

1
2
3
ssh-keygen -t rsa
cd .ssh
cat id_rsa.pub >> authorized_keys

将私钥和authorized_keys下载下来

id_rsa更名为jenkins_key,后面用于ssh登录。
authorized_keys上传到需要配置的服务器。

创建jenkins用户

以下为我写的创建用户脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh


## add user
adduser jenkins


# config key
su jenkins

cd /home/jenkins
mkdir .ssh
cd .ssh
wget http://192.168.1.1/authorized_keys
chmod 700 ../.ssh
chmod 600 authorized_keys

# config user
exit
passwd -u -f jenkins
usermod -p -u jenkins

配置权限

这时jenkins用户已经创建好了,但是这个用户目前还没有什么权限,以PHP项目为例,在构建时需要执行chown等命令,这时需要对jenkins单独赋予一些权限。

/etc/sudoers.d/下创建文件extra_sudoer

1
jenkins ALL=(root,nginx) NOPASSWD:/bin/chown, /bin/chmod, /usr/bin/php, /bin/bash

以上配置意思是,jenkins用户使用sudo命令执行chown等时不需要输入密码。

有一点需注意,使用sudo切换用户时需加-s选项,如:

1
sudo -u nginx -s php artisan route:clear

对于sudoers配置规则可以参考:sudoers的深入剖析与用户权限控制

以上。

记一次线上PHP项目问题

背景:

用户反馈,小程序和APP在早上8:30左右无法使用,于是着手排查问题。

排查:

项目架构为Nginx+PHP+Mysql

首先看nginx access和error日志,发现问题时间段出现大量499错误。简单说:499错误为nginx转发请求给PHP等待超时,响应给客户端的错误码。

难道那段时间PHP挂了?🤔

OK,看php-fpm错误日志,包括慢执行日志。项目使用的Laravel框架,通过错误日志可以看到执行入口文件public/index.php出错,返回Unknown:0

再看慢日志,发现在执行sql查询时耗时过长,怀疑是数据库出了问题,通过Mysql压力负载可以看到凌晨3点到8:30左右,压力较大。

这段时间执行了哪些任务呢?

通过排查,发现原来是通过mysqldump方式备份全库时,执行时间过长(数据库比较大)导致Mysql资源被占用过多,从而导致服务不可用。

对于数据备份,推荐Percona XtraBackup

排查问题也需要理解nginx和php-fpm的执行流程

补充nginxphp-fpm模型差异:

Nginx 是非阻塞IO & IO复用模型,通过操作系统提供的类似 epoll 的功能,可以在一个线程里处理多个客户端的请求。
Nginx 的进程就是线程,即每个进程里只有一个线程,但这一个线程可以服务多个客户端。
PHP-FPM 是阻塞的单线程模型,pm.max_children 指定的是最大的进程数量,pm.max_requests 指定的是每个进程处理多少个请求后重启(因为 PHP 偶尔会有内存泄漏,所以需要重启).
PHP-FPM 的每个进程也只有一个线程,但是一个进程同时只能服务一个客户端。
大多数的 Linux 程序都倾向于使用进程而不是线程,因为 Linux 下相对来说创建进程的开销比较小,而 Linux 的线程功能又不是很强大。

Jenkins使用简介

最近在做Jenkins的技术调研,目前已经把原有的PHP项目同步Jenkins部署了,对于Jenkins安装这里不做介绍,参考官方文档,非常简单。

说一下实现思路,以PHP项目为例:

过去:

  1. 主要依赖git进行发布,部署需要专门人员
  2. 前端代码源代码以及打包后文件都放在git中

缺点:操作麻烦,容易出错

现在:

  1. PHP发布不依赖git
  2. 一键发布,不依赖特定人员
  3. //todo 前端代码打包,合并将构建发布

简单画了个流程图:

jenkins流程图

Nginx配置HTTPS简介

申请证书

对于个人网站,建议使用Let’s Encrypt免费证书,目前Let’s Encrypt ACME v2 已正式支持通配符证书。
申请教程:Let’s Encrypt 免费通配符 SSL 证书申请教程

Nginx 配置证书

基础配置

证书申请好后,现在就开始配置了,示例配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 443;
listen [::]:443;
server_name example.mango.im;

ssl on;
ssl_certificate /etc/nginx/ssl/example.pem;
ssl_certificate_key /etc/nginx/ssl/example.key;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;

location / {
proxy_pass http://localhost:12345;
}
}

ssl_ciphers有一些弱加密,比较完整一个:

1
2
# Disable all weak ciphers
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

HSTS

以上配置了端口443,但一般情况下没有用户会直接输入https://,所以还是要配置80端口进行重定向

1
2
3
4
5
server {
listen 80;
server_name example.mango.im;
return 301 https://example.mango.im$request_uri;
}

这时访问example.mango.im即可跳转到https://example.mango.im,但这里还存在一个问题,聪明如你,对,当80到443跳转时依然可以被劫持、伪造。

所以这里介绍一下HSTS,什么是HSTS?

HTTP严格传输安全(英语:HTTP Strict Transport Security,缩写:HSTS)是一套由互联网工程任务组发布的互联网安全策略机制。网站可以选择使用HSTS策略,来让浏览器强制使用HTTPS与网站进行通信,以减少会话劫持风险.

HSTS的作用是强制客户端(如浏览器)使用HTTPS与服务器创建连接。服务器开启HSTS的方法是,当客户端通过HTTPS发出请求时,在服务器返回的超文本传输协议(HTTP)响应头中包含Strict-Transport-Security字段。非加密传输时设置的HSTS字段无效。

比如,https://example.com/ 的响应头含有Strict-Transport-Security: max-age=31536000; includeSubDomains。这意味着两点:

在接下来的31536000秒(即一年)中,浏览器向example.com或其子域名发送HTTP请求时,必须采用HTTPS来发起连接。比如,用户点击超链接或在地址栏输入 http://www.example.com/ ,浏览器应当自动将 http 转写成 https,然后直接向 https://www.example.com/ 发送请求。
在接下来的一年中,如果 example.com 服务器发送的TLS证书无效,用户不能忽略浏览器警告继续访问网站。

一般配置:

1
2
3
4
5
6
server {
listen 443 ssl;
server_name www.example.com;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

参考

阿里云OSS搭建静态网站

迫于想的太多,做的太少,买了几年的阿里云ECS也没有作出什么东西来,最近要到期了,准备放弃续费。但是之前备案的域名不想被审核掉(PS im域名现在已经不好备案了)。看了下阿里云的便宜的虚拟主机也是大几百,不太符合我的需求。

最开始是淘宝找了家有出口阿里云IP的虚拟主机,几十块吧,买了发现也就是能放个静态页面,管理面板非常老旧。今儿偶尔得知OSS也是可以挂个静态页面,域名CNAME解析过去就行,关键是OSS非常便宜,9块钱一年的存储空间,流量可以按量计费,如果只放个静态HTML其实用不了多少,图片等资源可以放到七牛上,免费的流量够用了。

顺手搭建了一个简单的JSON在线解析:http://www.mango.im/json.html

PHP增加X_REQUEST_ID

最近项目中需记录每个请求的request_id,从nginx追踪到php

这里使用nginx内置的变量$request_id(nginx 1.11.0 版本新增加的feature)

修改fastcgi_params增加

1
fastcgi_param X_REQUEST_ID $request_id;

这时通过打印全局变量$_SERVER即可看到我们刚刚定义的X_REQUEST_ID

获得X_REQUEST_ID后就可以用在具体的业务场景中了,比如我用Laravel记录日志,其中保存X_REQUEST_ID

截取部分代码:

1
2
3
4
5
6
7
8
9
10
$logger = new Logger('mango');
// 额外增加x_request_id
$logger->pushProcessor(function ($record) {
$record['extra']['x_request_id'] = app('request')->server('X_REQUEST_ID');
return $record;
});

$w = new Writer($logger);
$w->useDailyFiles(storage_path()."/logs/".$file.'.log');
$w->info($message = $addr, $context = $info);

补充:

如果要给laravel默认错误日志也加上X_REQUEST_ID,可以参考以下代码,路径:/bootstrap/app.php laravel5.5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/

// 日志增加x_request_id
$app->configureMonologUsing(function($monolog) {
$monolog->pushProcessor(function ($record) {
$record['extra']['x_request_id'] = app('request')->server('X_REQUEST_ID');
return $record;
});

$filename = storage_path('/logs/laravel.log');
$monolog->pushHandler(new Monolog\Handler\RotatingFileHandler($filename));
});

return $app;

Laravel 社会化登录简介

socialite作为官方包使用起来非常方便,目前支持大多数的社会化登录网站,比如微信,微博,Github等等。

具体支持哪些网站,参见列表:https://socialiteproviders.github.io/about.html

比如网站需要对接微信web授权登录,使用 https://socialiteproviders.github.io/providers/weixin.html 即可,文档已经写的比较完善,这里不再赘述。

举个微信实际例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Http\Controllers\WeChat;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;


class WeChatController extends Controller
{
public function oauth(Request $request)
{
$url = $request->input('url', ''); // 来源url
return Socialite::with('weixin')->redirectUrl(url("wechat/auth/callback?url=".urlencode($url)))->redirect();
}

public function callback(Request $request)
{
$oauthUser = Socialite::with('weixin')->user();
$request->session()->put('user_wechat', $oauthUser->user);
return redirect($request->input('url'));
}
}

在中间件中判断session是否存在,不存在调用oauth接口即可。可以说灰常方便了😀

记一次nginx代理配置

背景:

最近部署一台测试服务器在外网,主要用于微信等三方对接必须外网环境,然而数据库等依赖均在内网。

方案 1

打通外网服务器到内网VPN,所依赖的内网服务全走VPN到内网,由于数据库多条SQL执行,每次执行都要经历一次外网到内网的延时,速度实在无法忍受。

方案 2

由外网服务器做代理,转发到内网,所有依赖在内网中解决。

做了张简单的图

代理流程图
image

外网nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
server{
listen 443 ssl;
server_name test.com;
# 此处省略ssl的配置
location / {
# 内网的IP
proxy_pass http://192.168.8.12:81;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

内网nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 81;
server_name localhost;

charset utf-8;

root /var/www/test;

location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

配置好nginx还需要配置fastcgi参数,这里只列出改动项,根据自己需求更改。

1
2
3
4
fastcgi_param  REQUEST_SCHEME     https;
fastcgi_param HTTPS on;
fastcgi_param SERVER_PORT 443;
fastcgi_param SERVER_NAME test.com;

php7性能测试扩展php-xhprof-extension初探

最近发现测试环境接口响应巨慢,为了验证是哪里出了问题,遂引入性能测试。由于使用的php7,目前可用的性能测试扩展似乎只有php-xhprof-extension可选。

下载安装:https://tideways.com/profiler/downloads

以下为centos安装方法

1
2
3
4
5
6
echo "[tideways]
name = Tideways
baseurl = https://s3-eu-west-1.amazonaws.com/qafoo-profiler/rpm" > /etc/yum.repos.d/tideways.repo
rpm --import https://s3-eu-west-1.amazonaws.com/qafoo-profiler/packages/EEB5E8F4.gpg
yum makecache --disablerepo=* --enablerepo=tideways
yum install tideways-php tideways-cli tideways-daemon安装

安装好后,重启php-fpm

1
service php-fpm restart

如报异常:

Module ‘tideways’ already loaded in Unknown on line 0
可能是tideways重复加载了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

[root@test]# php -i | grep .ini$
Loaded Configuration File => /etc/php.ini
/etc/php.d/tideways.ini
user_ini.filename => .user.ini => .user.ini
vi /etc/php.d/tideways.ini
```
注释掉:extension=tideways.so

这时检查下扩展是否已经引用

php -m | grep tide
tideways
模块就做好了。

下面开始使用,官网文档使用是这样的:

```php
<?php

tideways_xhprof_enable();

my_application();

$data = tideways_xhprof_disable();

file_put_contents("/tmp/profile.xhprof", serialize($data));

其实就是两个函数的使用,但是当你使用时会报错,什么错误呢

PHP Fatal error: Uncaught Error: Call to undefined function tideways_xhprof_disable()
再次确认下tideways扩展有没有装好,可以用phpinfo()看一下。发现已经引入了模块。

这就很奇怪了。

再一番Google后,找到了这个问答的评论部分 https://stackoverflow.com/questions/38103784/how-to-use-tideways-on-own-server

Thank you for this. The projects own documentation contains incorrect commands (i.e. tideways_xhprof_enable() and tideways_xhprof_disable()) which meant all I could ever get was fatal errors. Remove the _xhprof token and it all works 🙂

啥意思呢?

就是把函数tideways_xhprof_enable();中的_xhprof去掉就可以了。

然后本人试了下,改成这样

1
2
3
4
5
6
7
8
9
<?php

tideways_enable();

echo "test";

$data = tideways_disable();

file_put_contents("/tmp/profile.xhprof", serialize($data));

这时就可以了,原来方法名都已经改了😳

运行完后可以读取文件,然后反序列化就可以拿到分析结果了。

类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<?php

array(
"main()" => array(
"wt" => 1000,
"ct" => 1,
"cpu" => 400,
),
"main()==>foo" => array(
"wt" => 500,
"ct" => 2,
"cpu" => 200,
),
"foo==>bar" => array(
"wt" => 200,
"ct" => 10,
"cpu" => 100,
),
)

关于对数据的处理和展示,后续有空的时候更新。

Laravel使用phpCAS注意点

项目使用框架为Laravel5.5,CAS客户端为https://github.com/apereo/phpCAS
注意事项:

具体使用可以参考phpCAS的docs/examples

附上一张CAS流程图:

image