Bowen's Blog

Respect My Authorita.

Mysql Access Control

| Comments

连接到Mysql的请求被接受或者拒绝的条件是:

  1. 是否通过身份验证
  2. 账号是否被锁定

身份验证利用了mysq.user表中的user,hostpassword字段,账号锁定与否是用account_locked 字段进行判断。身份的内容基于连接的客户端的主机和连接时使用的用户名。

当user字段为空时,意味着它可以匹配任意用户,不为空时,只有用户名和主机名都匹配时,才能进入下一个验证 的阶段。连接的用户名为空时,又被称为匿名用户,如果测试下本地的mysql user表,可能会发现如下的一些内容:

1
2
3
4
+------------------+--------------+---------------+
| user             | host         | password      |
+------------------+--------------+---------------+
|                  | localhost    |               |

这就是为什么我们可以用mysql命令直接进入mysql的控制台,因为它匹配到了来自本地的空用户名,空密码的 这条记录。

从这条记录也可以看到,用户的密码也可以为空,这意味着该用户登录不要指定密码。非空的密码并非明文存储, 而是通过PASSWORD()函数加密。

主机名不会为空,缺省为%,意为匹配任意主机。144.155.166.%意为匹配144.155.166的C类地址。

mysql在验证请求时,会将现有的mysql.user排序,然后先按照host,再按照user,顺序匹配。如这样的 mysql user表

1
2
3
4
5
6
7
8
+-----------+----------+-
| Host      | User     | ...
+-----------+----------+-
| %         | root     | ...
| %         | jeffrey  | ...
| localhost | root     | ...
| localhost |          | ...
+-----------+----------+-

排序后就变成

1
2
3
4
5
6
7
8
+-----------+----------+-
| Host      | User     | ...
+-----------+----------+-
| localhost | root     | ...
| localhost |          | ...
| %         | jeffrey  | ...
| %         | root     | ...
+-----------+----------+-

可以通过CURRENT_UER()函数来查看当前连接使用的用户信息。

之所以会总结关于mysql连接验证的知识,是因为最近在配置一个新的服务的时候遇到了这样的一个问题。 AWS Staging VPC通过Direct Connection连回数据中心,而在Staging环境的服务需要连接到数据中心 的数据库服务器。在数据库服务器添加账号的时候,Host的信息填的是NetScaler的地址,比如"192.168.%.%“ 但实际上应用服务器的网络属于"10.15.%.%",导致连接出错。话说我刚觉得应用服务器配置的DB地址应该用 NetScaler VIP的地址而不是DB服务器的地址…………,shit。


Reference:

  1. mysql connection access
  2. mysql request access

Speed Up Downloading Docker Images by Using Daocloud Mirror

| Comments

因为要试用下hyper这个新工具,不得不去下载docker镜像,不过官方的registry 简直慢如狗,加了VPN也很慢。以前有个docker.cn的镜像源也不见了,不过在搜索的时候发现了国内的一家容器 公司daocloud提供了免费的镜像服务。抱着试一试的态度,注册了个账号,在个人 的dashboard页面点击“加速器”,就可以看到定制的镜像链接,如“http://xxxxxxxx.m.daocloud.io%E2%80%9D%E3%80%82 下面的文档中给出了不同系统下配置镜像的方法,我的host是Ubuntu系统,需要做如下的修改:

1
2
echo "DOCKER_OPTS=\"\$DOCKER_OPTS --registry-mirror=http://d32e3878.m.daocloud.io\"" | sudo tee -a /etc/default/docker
sudo service docker restart

再试着pull一下新的image.

1
2
3
4
5
6
7
8
9
10
11
vagrant@vagrant-ubuntu-trusty-64:~$ time docker pull ubuntu
Pulling repository ubuntu
d2a0ecffe6fa: Download complete
83e4dde6b9cf: Download complete
b670fb0c7ecd: Download complete
29460ac93442: Download complete
Status: Downloaded newer image for ubuntu:latest

real  2m0.469s
user  0m0.034s
sys   0m0.034s

这是我在公司里面下载70m左右的容器镜像所耗时间,其实在家只要30s左右,公司网络太差了。

Anyway, 虽然镜像流量使用配额只有1000GB,但是一般情况下应该是够用了,为daocloud公司点个赞,良心企业。

Make Everything Production Like - (1/2)

| Comments

一般来说,软件生命周期涉及到的环境的典型划分如下:

  1. Development
  2. Test
  3. Staging
  4. Production

通常Staging和Production的环境非常的接近,差别可能只在于服务器的性能、数量以及数据的完整性等方面, 而且Staging/Production也会得到更多的照顾,什么DR,Infrastructure as code。各种monitoring,Active崩了有Inactive顶着,Master挂了,Slave顶上来,出了问题分分钟就有人来修好。 反观于Dev和Test环境,真是各种苦逼,14年Java经验的工程师设置了三天还没有把Ruby开发环境搞好,一 个前端工程师去解决为什么用Phantomjs在bamboo centos agent上运行测试不稳定,痛苦的要自尽。

除了这些,开发环境的不一致也会带来一些问题。最有名的就是“It works on my machine”。不过 这也不稀奇,毕竟不是所有人的开发环境和产品环境都是一致的。可能你在用Mac开发Ruby程序,但是在它 产品环境它却需要在Centos的机器上去运行。

对于开发环境来说,这些痛点总结来说,大概有如下几种场景:

  1. 开发环境和系统环境混在一起, 任何对于开发环境的修改可能会影响系统环境。
  2. 开发环境和产品环境的不一致。
  3. 本地开发时,除了运行必要的测试,还要启动多个依赖的服务,做一些手工的验证测试。 针对这些问题,有如下的解决办法。

使用环境隔离的工具

可以使用一些类似沙盒的工具去管理开发环境,比如,

  1. rbenv
  2. rvm
  3. chruby
  4. virtualenv
  5. jenv

前三个工具都是针对Ruby语言版本管理,开发环境下常用的是rbenv和rvm,测试和产品环境有可能用到chruby, 因为它更加轻量。这些工具本质上都是修改BIN PATH,cd等,以rvm为例,推荐的使用方式如下: 安装开发环境需要的ruby版本如2.0

1
rvm install 2.0.0

安装bundler,用来管理项目依赖

1
2
rvm use 2.0.0
gem install bundler

对于ruby的项目,将依赖的gem安装在工程的vendor/bundle下,而不是装在~/.rvm的gemsets目录下。 进一步将环境隔离到工程内,同时最好将vendor/bundle加在.gitignore的列表中,这样不会将gem提交 到repository中。

1
bundle install --path=vendor/bundle

virtualenv针对的是Python的环境管理,Jenv针对的是JVM系列的语言,Java/Scala/Clojure。 这些工具最大的作用在于项目和系统环境的隔离,不能用来保证环境一致性,一旦有系统环境变动,如, mysql版本升级,OpenSSL升级等,就得重新编译链接类库,很不方便。


用Vagrant解决环境一致性问题

我们很容易能够想到用虚拟机来解决环境一致性的问题,不用说,就是vagrant 这个工具。vagrant是一个跨平台的虚拟机管理工具,通过Vagrantfile,可以配置:

  1. 管理virtualbox或者VMware的虚拟机
  2. 以headless/gui的方式运行虚拟机
  3. 配置私有/NAT网络
  4. bash/puppet/chef/ansible多种provision的方式
  5. 共享/同步host/guest machine目录内容

Vagrantfile本身也可以看做是ruby文件,可以用ruby语言编写更加复杂的配置文件。vagrantcloud 提供了现成的base box,可以很方便的下载和发布box,比如,在vagrantcloud找到最新的Ubuntu14.04的boxubuntu/trusty64, 在工程中运行命令:

1
2
vagrant init ubuntu/trusty64
vagrant up

即可生成Vagrantfile并且启动虚拟机。对于一些开发环境要求比较复杂的项目,可以先用bash/chef/puppet 自动化配置的过程,然后运行vagrant provision配置即可。假设当前为gradle/java项目,guest共享的 目录为/vagrant,运行:

1
vagrant -c 'cd /vagrant && ./gradlew test'

即可在虚拟机中运行测试。vagrant package命令可以将当前的配置好的虚拟机打包为新的镜像,共享给团队的其他同事。这里有一点要注意的地方,虚拟机/etc/udev/rule.d/70-persistent-net.rules保存了当前虚拟机持久化网络设备udev规则。创建新的虚拟机时,新分配的设备和规则不符,导致网络出问题。所以在打包镜像之前,需要删除这个文件。


vagrant+docker解决依赖多个服务的问题

在微服务大行其道的今天,一个前端的工程可能会依赖多个微服务。前几天参加会议,还有人说微服务就是大家用 自己喜欢的语言/框架实现一个简单的服务。我替各位前端工程师们送他一句cnm,走好不送。 即便有类似Pact 这样的基于CDC的更加轻量的集成测试方式,还是需要真实的启动浏览器, 请求真实的服务去在本地做测试,尽快的获得反馈。在本地配置这样的e2e或者模拟产品环境,大致有如下的几种思路:

  1. clone所有依赖的工程,配置每个工程的环境,在不同的端口启动服务,覆写hosts配置,并且可能还需要 安装nginx做反向代理。这样做的成本比较高,而且对全局的环境影响比较大,也太过复杂。
  2. 用vagrant为其它微服务配置好虚拟机,同样可能需要覆写hosts或者安装nginx做反向代理。这样 启动多台虚拟机,开销太高,还能不能愉快的开发。
  3. 用vagrant+docker在一台虚拟机的多个容器上运行微服务,同时在虚拟机上用nginx作反向代理。
  4. 修改hosts,将其他微服务指向测试环境或者staging环境,这样做的代价较小,但是公用环境可能会导致 数据问题。而且如果同时修改前端项目和其中一个微服务,这样做可能就比较困难了。

不卖关子了,第三种方案也就是我一年前在项目中使用过的方案,剥离了业务相关内容后放在了github。设计思路可以参见下面的架构图,以及项目中的文档。 这个设计相对是比较灵活,因为可以根据nginx配置的不同,选择使用哪个环境上的微服务,同时host环境几乎不需要做任何修改。当时没有选择使用vagrant package的方式去打包配置好的镜像,因为这样生成的镜像太大,客户的程序员下载太慢。所以选择从头provision的方式,但是当时的docker版本较低,docker-registry的性能也比较差,pull image的速度确实慢,所以真正的用户只有我同事一个人o(╯□╰)o,现在要好很多了。 我想说的是这样的方式是可行的,当然还需要继续改进。

下一节,我会继续介绍如何让测试环境更加接近产品环境。

A Good Lesson on Security When Provisioning

| Comments

客户很多老的(legacy)系统都部署在租用的数据中心中,其中部署的方式大多是通过puppet去做zero-downtime 或者blue-green部署。绝大多数服务器都使用基于Centos的base image,其中包含基础架构需要的软件,如 nagios agent, splunk agent等。应用程序通过流水线打包为rpm package,上传到Koji, Koji将repo 分为三类,dev,staging,production,大致的持续交付流程如下:

  1. 代码提交后触发ci的build job,通过所有测试后生成package,上传到Koji,并tag在dev repo下面;
  2. 自动化部署测试环境,服务器的yum repo的配置中缺省enable dev,所以yum install即可更新package;
  3. 接受测试结束后,运行build去promote这个package到staging repo;
  4. 在部署的build中执行deploy staging 的dry-runjob和实际应用的job,由于staging服务器缺省 enable的repo是staging,所以它也会自动安装最新的package,并不需要你在puppet脚本中指定package的 版本号,ensure => latest即可。
  5. 产品环境的部署过程同staging类似。

看上去还不错,然而这里面有几个问题:

  1. 每次上传新的rpm包到koji时,它需要重新indexing去更新metadata,这个时间很长,可能有10几分钟, 会极大的影响持续交付流水线的时间
  2. 现在没有专门的团队去照看它,一旦koji出现问题,会极大的影响应用的部署上线时间

按道理说我们应该去解决Koji的indexing的问题,这样比较一劳永逸。但是客户的重心逐渐都转移到了AWS上, 所以不愿意再投入时间去修复这个问题,加之CI工具,Bamboo或者Jenkins都可以host rpm artifact,所以 后来的解决方案就是直接从Bamboo或者Jenkins取package,然后安装更新。我们在应用的部署上已经采取了这样 的方式,提交跑完流水线加部署到产品环境大概需要20分钟多点的时间,是以前的一半,当然这不是完美的解决方案, 如其中有安全问题,如产品环境(数据中心)需要通过代理去访问AWS部署的CI的artifact。不过作为过渡方案, 目前来说大家还都能接受。

好,该说到今天的问题了,Koji服务器不能显示导入的package,致使一个应用不能部署最新的package,其中 包含一个比较紧急的bug修复,于是客户问我试试通过Jenkins去获取rpm包进行部署,绕过Koji。

首先,我做了如下的测试:

  1. 下载Jenkins host的rpm包是否需要验证(用户名/密码),答案是否定的,Bamboo需要验证,Jenkins的 安全性比较差,but anyway.
  2. 是否能在staging和production服务器上下载在Jenkins上的package,答案也是可以。

那么我就有了一个大致的思路,通过传入buildnumber, 用puppet脚本调用wget等命令将package下载到服 务器的/tmp下,然后在package定义中,通过指定rpm文件,进行local install.

1
2
3
4
5
6
package { "my-app":
   provider => "rpm",
   ensure => "$version",
   source => File["/tmp/$package_name"],
   require => File["/tmp/$package_name"]
 }

因为对puppet的type理解不足,我总以为File["/tmp/$package_name"] 返回的是文件的路径,但是 没想到返回的却是一个字符串,类似这样"file /tmp/my-app-version.rpm",所以rpm update总是 失败。因为有点着急,我就开始各种hack,通过Exec类型,执行yum localinstall的方式更新,然后, 我就被客户的安全专家怒喷……,针对我的puppet修改提交,他提出的几点关于安全方面的原因如下:

  1. puppet脚本不应该用root账号去执行,而是应该用一个专用的限定权限的账号,如puppet,并且只授予安装 删除以及在某些特定目录下修改文件的权限,这样它就不能执行一些其他的命令;
  2. 将文件放在/tmp下是非常危险的,因为当黑客通过web应用程序漏洞获得www用户权限时,他可以将一些 恶意的文件(如包含恶意脚本的rpm包)放在/tmp下,这时候如果我安装rpm包,很有可能中招。

大神在喷完我后给出了几点建议:

  1. 鉴于情况比较特殊,所以可以沿用当前的方式,但是可以将文件下载到如/opt之类只有root用户组才能访问的 目录中;
  2. 可以考虑使用S3作为yum的repository(这个以前我们就有讨论过,以后应该就是这种方式了)

我觉得他说的很有道理,所以先静下心来,找到了为什么source文件找不到的原因,然后修改了保存pacakge的 目录,最后的package定义脚本如下:

1
2
3
4
5
6
package { "my-app":
  provider => "rpm",
  ensure => "$version",
  source => "/opt/$package_name",
  require => File["/opt/$package_name"]
 }

虽然被喷有点尴尬,但是学到了东西感觉还是很开心的,以后要更加稳重些,多找下root cause和best practice。

One Company

| Comments

客户公司的前任CEO有次来办公室访问,提到一个很有意思的话题,就是公司的任何一个员工或者管理者,都要从 一个公司“One Company”整体的利益出发去考虑问题。看上去是任何一家公司或者党政机关领导冠冕堂皇的话,实际上 是很难做到这点的。已知的一个比较成功的例子是中国共产党,在高达的帮助下,他们全国一盘棋的策略,战胜了 国军,夺取了政权。

联系到一个客户有次问我的一个问题: 如果你在团队A,在使用团队B开发的工具的时候遇到一个问题,你是要通过 hack的方式绕过这个问题,还是投入时间,修复这个问题,发pull/request,跟踪问题直到该问题得到解决? 我很诚实的告诉他,It depends。因为我做的是交付项目,如果我的直接客户,也就是对方的项目经理,在意 我当前任务的交付时间,我会将这个问题在github上提交一个issue或者在slack channel通知团队B。如果 他认为这问题,是公司开发团队都要面临的问题,并且认为停下我当前的任务,去修复这个问题是值得的,那我就 去做。

我想说的是我们的客户会更乐意让我修复这个问题,但是我从知乎上看到的国内的这些大小互联网公司,我觉得 如果他们的员工遇到同样的问题,可能不会选择去修复这个问题,其中很重要的原因就是KPI。我从毕业开始就 一直在一个奉行敏捷文化的小公司工作,没有机会体会大公司的文化,但是就我了解到的,大公司的KPI造成了 人与人,团队与团队之间的对立。Dev的考核变成加班时间和代码行数而不是代码质量,QA的考核变成查出的bug 数而不是更关注产品的质量,Ops的考核变成Incidents的数量而不是运维的质量,再往上点就变成了团队资源 争夺以及政治斗争。

我曾经参与过一个大型的通信公司关于持续交付的讨论,讲到最后考评制度,大家都纷纷开始叹气,真是一件让人 觉得悲伤的事情,为什么不能把KPI定义为产品的成功与否,或者团队的成功与否,如此QA,Dev,Ops,PM能 够协力去将产品变得更好。

有感于最近知乎上关于陈皓离职以及阿里的HR事件的一些讨论,随便扯了些,可能是这个世界太复杂,我想的太简单了。

Riding Accross Taibai Mountain

| Comments

五一假期期间和同学从西安骑到了汉中,全程约360KM,均速约18.3KM/H。经过路线大致为西安-省道107(关中 环线)-眉县(眉太线)-太白县-汉中,其中穿过太白山的路线基本上就是重走以前的褒斜栈道。具体路线如下:

褒斜栈道的历史背景

提到褒斜栈道,很多人第一时间想到的应该就是三国 时期,诸葛亮北伐出斜谷,走的就是这条道。其实,褒斜道由来已久,武王伐纣时,蜀王相助出兵,走的就是这条 道。《大秦帝国》中描述的司马错收巴蜀,也是在秦国修复的褒斜栈道的基础上进入汉中。此后历代都有在修复和 使用。整条栈路南起褒城,北出斜口,沿着褒河,逆流而上。也是从蜀地到关中(长安)最容易的一条道路,在古代的 军事以及经济文化交流上起着非常重要的作用。

骑行过程

我们从在4月30号下班后出发,接连骑行了两天,于5月2号下午5点多到达汉中市。简单的介绍下这几天的过程。

第一天

上班前就带好了各种装备,下班立即从软件园出发,先到郭渡和朋友集合,然后往北直走到沣裕口,向西进入s107 沿着关中环线和秦岭一路向西骑。时间到八点左右,天色变暗,骑行速度大减,从西边过来的车都是开远光灯,完全 失去了前面路段的视线,麻痹诅咒你们天天被贴条,太没素质。107省道比较好的地方就是有分割开的自行车道, 这样不用担心一头撞到汽车上去,不过还是出现了几次差点撞到道牙子的情况。9点多吃完饭后,狂风大作,逆风 实在太难受,除了风阻,还有各种砂石,噼里啪啦的打在身上,比中冰雹还惨。堪堪的在11点前骑到了楼观镇, 附近的楼观台就是传说中老子讲道的地方。很顺利的找到了旅店,标间100,有无线可洗澡。不过因为太累,洗完 倒头就睡着了。

第一天骑行70km,从锦业一路软件园到楼观镇。

第二天

醒来已是早上7点多,果然如天气预报所言,下雨了,而且还是中雨,没办法,吃完早饭,只能继续往前骑。后悔 车子上没有装全包的挡泥板,没有带大点的雨披。朋友的装备要更好些,所以只有裤腿湿了。看来出远门不能怕丑, 实用最重要。国道108和310比环线的107省道路况要差些,所以一早上都骑的很慢,到下午1点才来到眉县,五个 多小时骑了60km。万幸雨停了,这样就可以安全的进山了。吃了海量的干拌哨子面和一口香,又充满了力量!

图片

进入眉太线后不久,就开始了爬坡之旅,刚开始就是个200米的陡坡,不过后面就是些缓坡了。先过温家山隧道,

图片

之后就是石头沟水库。一路上都是太白县的宣传画,很多都是介绍三国时期发生在这条栈道上的故事。海拔逐步 升高时,就可以看到太白山的积雪。过了衙岭,就到了眉太公路的最高点,海拔大约有1700多米。此时距太白县 约有10km,一路缓下坡,可以多蹬几下,很快就到了太白县。

图片

这里的旅馆很奇怪,很少有带卫生间的标间,洗浴都是公共的,不过好在价格低廉,只要88块甚至更加便宜。洗完 出门,费了好大劲才找到吃饭的地方。夜晚的太白特别冷,冻得人直哆嗦,吃完回去钻被窝就睡着了。

第二天从楼观镇经眉县到太白县,共130km。

第三天

早早的起了床,虽然是大晴天,但是依然很冷,把能穿的衣服都裹在了身上,吃了豆花和饼,向汉中挺进。从太白 县到汉中,海拔会下降510米,所以这一路基本上就是缓下坡了,速度也不会特别快,这样,可以将眼睛解放出来 看看风景。我们的右手边是褒河,周围是群山环绕,在这河谷中穿行,听着鸟语,闻着花香,感觉真是棒棒。唯一 影响心情的是不断出现的隧道,还有偶尔遇到的车祸事故,一个可怜的卡车司机翻车了,还好他没有事。 我们用了4个多小时就到达了武关驿镇,走了约90多公里。武关驿镇也是古时候的褒斜栈道中设立的驿站之一。武关 驿镇的西北边是留坝县,我亲戚告诉我说这是一座在森林中的城市,可惜这次由于时间原因不能去了,下次可以从 G316经留坝到汉中。

图片

因为正在修几条高速公路的缘故,来往的大卡车很多,一些路段的路况也不是很好。离开武关10km左右,就是传说 中萧何追韩信处,成就了历史的佳话,同时目送一处即将要上演的悲剧。过了青桥驿镇不久,就到了赤崖,这里是 现存的三处古栈道遗址之一。诸葛亮一出祁山,赵云邓芝守萁谷,被魏将曹真击败,两人退到赤崖,这两个逗比为了 防止魏军追击,烧了北边上百里的栈道,不成想天降大雨,将赤崖以南的栈道又冲毁,两人只好屯兵赤崖。

图片

赤崖附近的遗址不少,还有诸葛亮屯兵处等。 继续前行就是石门水库,或者石门栈道,曾经这里有很多的遗址,后来因为修水库,都迁到了汉中市内。水库过去是 个大下坡,路上有个大坑,差点骑进去。下了坡,距汉中市只有15km,道路宽又平。到汉中已经过了5点,先去 火车站把车子托运,路上买了点特产,西乡牛肉和汉中仙毫,就赶紧去高客站买票。6点半发车,时间比较紧张,都 没有时间去吃下汉中著名的小吃米皮。 第三天,从太白县到汉中,共骑行160km。

总结

一路骑行下来,共完成以下成就:

  1. 逆风骑行
  2. 雨中骑行
  3. 爬坡
  4. 被狗追
  5. 一命通关

需要改进的地方有两点:

  1. 长途骑行,车子一定要加装全包的挡泥板,雨披也得带大点的,还有些塑料袋,防水也可以罩脚
  2. 骑行过的路线,如果没有什么趣味,可以直接跳过,其实我们可以提前托运车子,然后从宝鸡出发,直接穿太白

古代汉中通往关中的道路有四条,褒斜道、故道、傥骆道、子午道。其中傥骆道已经不复存在,比较接近它得是 国道108穿过黑河,佛坪,洋县到汉中的路。相比褒斜道,那条路风景的风景更好,骑行的难度更大,沿途的补给 也更为困难。除了隧道过多,褒斜道的骑行难度是比较低的,除了缓上坡之后就是150km的下坡,一路上都是历史 文化遗迹。故道又称陈仓道,就是“暗度陈仓”那条栈道。上次本来有机会走的,因为大雨放弃了,后面有机会要去 试试,直接到宝鸡,然后再走陈仓道,经凤县到汉中。子午道略近似于现在的210国道,经分水岭,宁陕到洋县最后 再到汉中。这条路应该是最有挑战的,因为要穿过分水岭,一路的风景也要更加迷人,将来也一定得走一次。

途中用GoPro拍得一些视频,忘带充电线,导致断断续续的只录 了不到30分钟。

Bag of Tricks

| Comments

客户的程序员和Ops大都很有经验而且很聪明,很多有用的知识都是和客户程序员/Ops结对的时候学到的。我从 其中一个客户那里,学到了一句很有意思的话叫做“bag of tricks”。

这个是什么意思呢?大意就是说,首先解决问题要有多种思路,至于你用到那种是另外一回事;其次,相对来说, 细节没有特别重要。

这也就是为什么IT领域的人需要多学习或者了解前言技术和知识的一个原因,丰富你的技能包,可以不精通,但是 要知道它能干吗。比如部署的工具,有Chef,Puppet,Ansible,Babushka等,这样在你做部署的方案的时候 就更加容易些。

Facter

| Comments

facter是一个以key/value形式收集系统信息的工具。

1
2
3
4
5
[root@localhost ~]# yum provides /usr/bin/facter
1:facter-1.6.17-1.el6.x86_64 : Ruby module for collecting simple facts about a host operating system
Repo        : installed
Matched from:
Other       : Provides-match: /usr/bin/facter

Ansible和Puppet都用它来收集系统信息。

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# facter
architecture => x86_64
boardproductname => VirtualBox
facterversion => 1.6.17
hardwareisa => x86_64
hardwaremodel => x86_64
ipaddress => 10.0.2.15
ipaddress_eth0 => 10.0.2.15
.......
operatingsystem => CentOS

facter是跨平台的,所以可以利用它来获取不同服务器的信息。 比如Ansible中有专门的facter模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
~> ansible vagrant -m facter -i hosts
vagrant | success >> {
   "architecture": "x86_64",
   "augeasversion": "0.9.0",
   "changed": false,
   "domain": "localdomain",
   "facterversion": "1.6.17",
   "fqdn": "localhost.localdomain",
   "hardwareisa": "x86_64",
 ......
 ```

puppet中,可以如此使用:

puppet case $::operatingsystem { ‘CentOS’: { include centos } ‘MacOS’: { include mac } }

1
2
3
4
因为facter的变量都是global的,所以直接使用`$operatingsystem`也是可以的。

有两种方式可以添加自定义的facter变量(fact),其中通过环境变量的方式会比较容易,只要环境变量名是以`FACTOR_`
开头,都可以通过facter读取到该变量。

bash ~> export FACTER_cartman_is_gay=“true” ~> facter | grep cartman cartman_is_gay => true

~> export FACTER_KYLE_MOM_IS_BITCH=“true” ~> facter | grep kyle kyle_mom_is_bitch => true

~> export FACTER_cartman_is_gay=“false” ~> facter | grep cartman cartman_is_gay => false

1
2
可以看出,fact变量的大小写无关的,自定义的fact可以被override的,puppetlabs的文档中说系统fact
不能被override,但是貌似可以,不知道哪里出了问题。

bash [vagrant@localhost ~]$ facter | grep operatingsystem operatingsystem => CentOS operatingsystemrelease => 6.4 [vagrant@localhost ~]$ export FACTER_operatingsystem=“Rock” [vagrant@localhost ~]$ facter | grep operatingsystem operatingsystem => Rock operatingsystemrelease => 2.6.32-358.el6.x86_64 ```

我们老一点的系统都部署在数据中心,所有的服务器的配置用puppet管理,基本上都是虚拟化,基于一个标准的 镜像,标准镜像spin up的服务器中会添加一个自定义的fact,用来判断是否是自定义的系统。我其实不太喜欢 这样的使用方式,首先这让puppet manifest的可读性降低了,找遍整个repo都不找不到在哪里定义的这个变 量,其次,让puppet manifest对系统产生了依赖性,不够灵活。


1

Puppet Cron Pitfall

| Comments

最近完成了一个任务,内容是更新一个cron运行perl脚本的连接数据库的credential。该脚本用来清除数据库中一些 过期的匿名用户,脚本部署在一个内网的服务器上,服务器的配置由puppet管理。脚本一直要保持运行,所以在 脚本中会检查pid的modified time,如果小于一个小时,则进程退出。因此,在脚本第一次运行后,以后的cron 运行的进程会自动退出。 所以,我的计划是这样的:

  1. 更新puppet管理的脚本内容中数据库连接的credential
  2. 更改puppet中服务器中cron运行的时间为每两分钟运行一次,这样,下次运行时,脚本pick up新的配置
  3. puppet --test应用配置更改
  4. 停止正在运行的脚本,pkill process
  5. touch -t some_old_time_stamp
  6. 监控log,等待脚本启动
  7. 监控数据库,是否有新的credential连接进程, mysql -e "show full processlist;" | grep new_user
  8. 一切运转正常,将puppet中cron的时间改回去并应用

关于cron配置的代码原本如下,原本cron运行的时间为23:55:

1
2
3
4
5
6
cron { test:
  source => "some scripts",
  user => root,
  hour => 23,
  minute => 55
}

应用后生成的cron应该如此: 55 23 * * * some scripts 更改后的puppet配置如下:

1
2
3
4
5
cron { test:
  source => "some scripts",
  user => root,
  minute => "*/2"
}

期望应用后的cron应该如此: */2 * * * * some scripts 一切都非常顺利,直到step 6,等待日志输出时,发现时间过了4分钟还是没有日志,WTF!赶紧看了眼更改后的 cron运行时间:

1
*/2 22 * * * some scripts

顿时就震惊了,鉴于是产品环境,不敢担待,于是直接就顺手改了,两分钟后运行,如释重负。由此可见,puppet cron的这个地方是略坑的, 如果更改比较多,最好是指定完整的时间配置,如:

1
2
3
4
5
6
7
8
9
cron { test:
  source => "some scripts",
  user => root,
  year => "*",
  month => "*",
  day => "*",
  hour => "*",
  minute => "*/2"
}

话说这还不是最坑的地方,因为脚本还是没有顺利运行,真是日了狗了,不得不手动revert脚本,然后让脚本正常 运行,然后再重新查看脚本,测试,查找问题在哪里。 最后的原因实在是让人崩溃:

1
die "ERROR: Missing or invalid database username '$dbuser'" unless (defined($dbuser) && $dbuser =~ m/^[a-zA-Z0-9]+$/);

新的dbuser名字中包含了连接线-,因为不能满足regex检查,所以程序直接退出了。实在是不能理解为什么 会有这样的约束条件,我只知道mysql数据库用户的名字不能超过16个字符,但是没听说过限制数据库用户名字格式 是个什么鬼。实在是无语,不过,我能理解,不会挖坑的程序员不是好程序员。

Essential Curl

| Comments

什么是curl


curl是我们在Linux下常用的下载文件或者测试web服务的工具,罗列下他们常 用的一些场景,当做总结。curl支持多种网络协议,包括最新的HTTP/2。

获取网页源码


1
2
3
4
5
6
7
~> curl www.google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.com.hk/url?sa=p&amp;hl=zh-CN&amp;pref=hkredirect&amp;pval=yes&amp;q=http://www.google.com.hk/%3Fgws_rd%3Dcr&amp;ust=1423205620492528&amp;usg=AFQjCNGJFP3kOpmF9vbNWK_cgRr7G5yffQ">here</A>.
</BODY></HTML>

文件下载


curl缺省会输出文件的内容,用下面的命令可以用curl将文件保存到指定的目录,curl比较苦逼的地方在于, 如果不是保存当前目录,需要自己指定要保存的文件名。

1
2
~> cd /opt/ && curl -O http://ergonlogic.com/files/boxes/debian-current.box
~> curl -o /opt/current.box http://ergonlogic.com/files/boxes/debian-current.box

多线程下载


curl不支持多线程下载,我用过的一个多线程下载的工具是aria2,蛮好用的,而且这货还支持bt,ed2k等p2p协议下载。

1
~> aria2c -x5 https://github.com/jose-lpa/veewee-openbsd/releases/download/v0.5.5/openbsd55.box

通过代理访问资源


有两种方式可以让curl使用proxy,第一种是通过设置环境变量,http_proxy, https_proxy等,用no_proxy去设置屏蔽代理的列表。

1
~> export http_proxy="http://proxy:3128"

让proxy对一些地址无效。

1
~> export no_proxy="127.0.0.1,localhost.localdomain"

另外一种是在运行命令时直接指定使用的proxy或者不使用proxy。

1
2
3
~> curl -x "http://proxy:3128" www.google.com
~> curl -x "socks5://proxy:3128" www.google.com
~> curl --noproxy * www.google.com

curl的--noproxy是需要指定一个exclude的列表。

花式下载和断点续传


对于很规则的下载url,可以通配符或者正则来处理,比如我想下载自己博客的几个图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~> curl -O http://iambowen.github.io/images/[1-3].png

[1/3]: http://iambowen.github.io/images/1.png --> 1.png
--_curl_--http://iambowen.github.io/images/1.png
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
100  222k  100  222k    0     0  59816      0  0:00:03  0:00:03 --:--:-- 59808

[2/3]: http://iambowen.github.io/images/2.png --> 2.png
--_curl_--http://iambowen.github.io/images/2.png
100  342k  100  342k    0     0  33117      0  0:00:10  0:00:10 --:--:-- 48607

[3/3]: http://iambowen.github.io/images/3.png --> 3.png
--_curl_--http://iambowen.github.io/images/3.png
100  208k  100  208k    0     0  32245      0  0:00:06  0:00:06 --:--:-- 39914

断点续传,除了保证文件完整性,还可以检查文件是否有变动,如果有变化则更新,没有则保持文件原有状态。

1
~> curl -O -C http://ergonlogic.com/files/boxes/debian-current.box

获取headers信息


有时候,我们只需要看到返回的headers信息,查看cache是否命中,或者返回码。

1
2
3
4
5
6
~> curl -I www.baidu.com
HTTP/1.1 200 OK
Date: Tue, 10 Feb 2015 08:25:12 GMT
Content-Type: text/html; charset=utf-8
Connection: Keep-Alive
......

传说使用telnet命令可能拿到更多的http headers信息,无法验证,方式如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
~> telnet 0 4000
Trying 0.0.0.0...
Connected to 0.
Escape character is '^]'.
GET / HTTP/1.1
HOST: 127.0.0.1:4000

HTTP/1.1 200 OK
Etag: 232a2c-320c-54d9ce22
Content-Type: text/html
Content-Length: 12812
Last-Modified: Tue, 10 Feb 2015 09:23:46 GMT
Server: WEBrick/1.3.1 (Ruby/1.9.3/2014-11-13)
Date: Tue, 10 Feb 2015 09:29:24 GMT
Connection: Keep-Alive

follow redirection信息


有时候访问的资源,可能被临时或者永久转移,所以会有中间跳转的过程,但是直接去curl是拿不到完整信息的。

1
2
3
4
5
6
~> curl -I www.google.com
HTTP/1.1 302 Found
Location: http://www.google.com.hk/url?sa=p&hl=zh-CN&pref=hkredirect&pval=yes&q=http://www.google.com.hk/%3Fgws_rd%3Dcr&ust=1423564147298386&usg=AFQjCNHZ7kXJXOfOQyCSOQ6ZGOPjeVaIYg
Cache-Control: private
Content-Type: text/html; charset=UTF-8
......

通过下面的方式可以拿到所有的返回信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
~> curl -LI www.google.com
HTTP/1.1 302 Found
Location: http://www.google.com.hk/url?sa=p&hl=zh-CN&pref=hkredirect&pval=yes&q=http://www.google.com.hk/%3Fgws_rd%3Dcr&ust=1423559822825114&usg=AFQjCNEEY2qyq9HghaQVZ89ugMv9kvDlLA
......

HTTP/1.1 302 Found
Location: http://www.google.com.hk/?gws_rd=cr
......

HTTP/1.1 200 OK
Date: Tue, 10 Feb 2015 09:16:35 GMT
Expires: -1
......

加入验证的请求


有时候在请求一些资源时,需要通过验证才能完成访问,可以用username:password加URL即可。

1
~> curl http://user:password@echo.httpkit.com?queryString

伪装user agent的请求


有的服务器可能会限制访问的User-Agent类型,用curl测试的时候可以用相应得参数进行伪装。

1
2
3
4
5
6
7
8
9
10
11
12
13
~> curl  echo.httpkit.com
 "headers": {
   "host": "echo.httpkit.com",
   "user-agent": "curl/7.37.1",
   "accept": "*/*"
}

~>  curl -A "Bad Ass"  echo.httpkit.com
 "headers": {
   "host": "echo.httpkit.com",
   "user-agent": "Bad Ass",
   "accept": "*/*"
 },

发起HTTP请求


-X可以指定发起请求的HTTP方法, 如果用POST或者PUT等方法,可以用 -d指定request body,-H可以指定请求的一些Headers。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
~> curl -X PUT -H 'Content-Type: application/json' -d '{"firstName":"Kris", "lastName":"Jordan"}' echo.httpkit.com
{
 "method": "PUT",
 "uri": "/",
 "path": {
   "name": "/",
   "query": "",
   "params": {}
 },
 "headers": {
   "x-forwarded-for": "210.74.157.146",
   "host": "echo.httpkit.com",
   "user-agent": "curl/7.37.1",
   "accept": "*/*",
   "content-type": "application/json",
   "content-length": "41"
 },
 "body": "{\"firstName\":\"Kris\", \"lastName\":\"Jordan\"}",
 "ip": "127.0.0.1",
 "powered-by": "http://httpkit.com",
 "docs": "http://httpkit.com/echo"
 }

上传文件


1
~> curl --form "fileupload=@filename.txt" http://hostname/resource

处理cookies


保存cookies到本地

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 ~> curl -c echo.cookies  http://www.baidu.com > /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 88672    0 88672    0     0   298k      0 --:--:-- --:--:-- --:--:--  297k
 ~> less echo.cookies
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

.baidu.com      TRUE    /       FALSE   3571557287      BAIDUID 287BE3DB1621FDE977C39D21BD02CA45:FG=1
.baidu.com      TRUE    /       FALSE   3571557287      BAIDUPSID       287BE3DB1621FDE977C39D21BD02CA45
www.baidu.com   FALSE   /       FALSE   0       BDSVRTM 0
www.baidu.com   FALSE   /       FALSE   0       BD_HOME 0
.baidu.com      TRUE    /       FALSE   0       H_PS_PSSID      10422_1449_11089

使用cookie 发起请求

1
~> curl -b echo.cookies  http://www.baidu.com > /dev/null

fail on error


对于服务器端错误,http请求没有任何输出,curl的返回为0,使用-f参数可以在遇到服务器错误是返回非0(22)。 最近在完成一个用Bamboo host的package去部署的任务的时候用到了这个参数,一旦下载package出错,部署会中断。

1
2
3
4
~> curl -f http://iambowen.github.io/ksjdkfsjdf
curl: (22) The requested URL returned error: 404 Not Found
~> echo $?
22

从浏览器"偷取"curl的命令


觉得curl指令太难记忆,可以直接从浏览器中"偷"取, Chrome提供了这样的功能:

chrome

References


1 2 3