其中的一条无用的warning日志信息显示如下:
1
|
|
一周内这条warning的数量超过百万条,还是比较可观的。简单查询下其原因是httpclient
里面的getResponseBody()
调用触发的。
1 2 3 4 5 |
|
看完这个后我们的理解是,有些请求的response body过大,超过缺省的1M(代码会从Content-Length
header中获取这个大小),就会触发这个warning,当时没有意识到还有可能是确实不知道response body的长度。
相关的方法调用在代码中有10几处,当时我们也无法定位那段代码引发了这个问题,无脑修改的话,成本比较高,可能要增加一些测试用例,以及做回归测试。所以当时就想着用成本最低的方式修改,从配置文件中给BUFFER_WARN_TRIGGER_LIMIT
赋一个更大的值,如20M,毕竟这是个遗留项目,熟悉代码的人以及比较少了。没有选择调整日志的级别是因为HttpMethodBase
类是个超类,粗暴调整可能会掩盖其它有用的warning日志。
部署完成后比较日志数量发现并没有太大变化,不得不让我们重新回来审视这个问题的根本原因在哪里。幸好当时系统加了一个transactionID的功能,每次的请求过来时,在应用中用UUID生成一个transactionID写入应用日志,response返回时再写入access log。这样我们就在请求和对应的代码调用之间建立了联系。
功能上线后重新在splunk中搜索,立刻就定位了是在请求Google Map API时触发了这个warning,而且在本地可以稳定重现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
然后发现原来返回是没有Content-Length
的:(。
Content-Length
header是客户端用于了解服务器返回body的大小,从而在获得等大的内容后,结束连接,节省开销。但在实际的应用中,Content-Length
有可能无法准确反映返回body的大小,其值过大会导致pending,过小内容又会被截断。
Transfer-Encoding: chunked
是用来分块编码传输内容,每个分块中包含了长度值和数据,最后一个分块长度值是0,这样就可以准确知道边界了。
定位到问题在哪里之后就很容易解决了,最后只要改动一行代码:
1 2 |
|
回过头来反思整个过程,因为是遗留系统,所以处理的方式有些粗糙,如果当时我们遵循下面的过程也许会更好些: 1) 定位问题,找到根本原因(有transactionID的配合会更方便),而不是盲目用生产环境来测试配置的正确性; 2) 在本地重现问题,应用解决方案,并与熟悉遗留系统的同事沟通 3) 回归测试后上线
]]>我们暂且把这个外部的服务称为service.mycompany.com,这个服务分别部署在澳洲和欧洲的两个数据中心,入口处是Akamai,做负载均衡,尽可能的按照访问来源去分发请求。
该微服务部署在AWS悉尼的数据中心,所以理论上来讲,当它请求service.mycompany.com时,Akamai应该返回的是位于悉尼的edge节点的IP,同时其访问的origin服务器也应该位于悉尼。但是通过在该微服务的服务器debug,发现ping值以及traceroute的值都比较高,办公室访问却都一切正常。当时怀疑是Akamai的GEOIP判断出了问题,把来自亚马逊悉尼的请求当成了来自美国的IP的请求,于是用部署于欧洲的数据中心的服务处理请求。和基础设施部门管理网络的人讨论,再次调查后结论类似。
问题出在这个AWS账户下的VPC的DHCP options的配置。因为是比较早期使用的share的AWS账户,所以下面的网络配置比较复杂,配置有Direct Connect 连往其他数据中心,以及很多VPC Peering。不知道因为什么原因,这个微服务部署的Cloudformation template里面选择了包含google DNS 8.8.8.8
和8.8.8.4
的DHCP Options。我们都知道对于在Akamai上注册的服务service.mycompany.com来说,如:
1 2 3 4 |
|
第一次DNS请求返回的记录是CName,之后进一步返回Akamai动态DNS的CName,也就是edge server的CName,之后再根据DNS服务器返回对应的edge服务器的IP地址,如果查询的是Google的DNS,那么它会返回美国的edge服务器地址……。我们可以测试下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
查询下IP地址信息,
1 2 |
|
所以,这个微服务的请求先到Akamai美国的edge服务器,之后很有可能请求被发送到了欧洲的origin服务器,这个延迟不增加才👻了……。
解决的办法很简单,更新配置,DHCP Options选择Amazon提供的DNS就可以了,响应时间就降下去了。
这个事情给我们的教训就是,不管怎么样都不能崇洋媚外,虽然澳洲一直follow美国,但是DNS还是得用自己的。
]]>:bicyclist::skin-tone-2: :house: :thunder_cloud_and_rain: :disappointed:
。更甚者会在git commit message中添加emoji。比如像这样
1
|
|
意思是完成这个开发的需求是和xiao
结对做的。pull request发出后,review的人除了会发:+1:这样的表情表示支持外,还会用:shipit:,:ship:之类的表示赞同,可以merge。
这样的emoji为开发增添了乐趣,但是有时候也会带来麻烦,比如我今天就遇到了这样的情况。
在清理完一些旧代码后,我在提交信息里面➕了下面的消息:
1
|
|
提交merge后,过了一段时间看了下build,还没有到运行阶段就挂了。查看了下原因,发现是bamboo在保存提交信息时遇到了一个复杂字符出错了。
1
|
|
当时就感觉是这个emoji出问题了,搜了下提示的编码的十六进制,果然是这个原因……。没办法只好reset下,push force,再重新修改提交信息再push。
同事告诉了我这个事故的根本原因是mysql的utf-8对Emoji的支持不够,解决的办法就是把数据库的charset设置为utf8mb4
,详见这篇文章.
所以,以后玩emoji的时候一定得先确认系统支持,否则可能会带来一些:shit:。
1 2 |
|
这样的提交信息的问题在于不表意,没有简要的说明修改的内容,为什么要这样的修改,别人只能去查看具体的代码改动才能知道发生了什么,但是可能无法知道为什么这样修改。当然,这样的提交我自己也写过,原因包括
1. 语法、拼写错误,羞于示人
2. 解释原因得写很长,懒的敲键盘
3. 无法解释为什么这样的修改就能work
这其实算是一种比较不负责任的行为,估计别人看到会比较崩溃,幸运的是还没有领导看到,所以至今没有被开除。举个例子,假设某个提交引起了产品环境的错误,别人需要迅速定位是哪个提交引起的问题,但是如果提交都是类似Perfectly complete a new story
,而且每次代码修改的量都比较庞大,那就得花很多时间才能定位。相反如果提交信息很清晰,BAU-1008 add xxx form in xxx page. :pear: Kevin
。你用git log --oneline --after "Aug 10 2016"
可以迅速看到对应的提交,进一步的可以查看修改内容再查找具体的问题。
今天早上客户跟我们一起做了一个关于如何有效的提交git commit
信息。他提到了git commit message
的7个规则。他认为从项目维护性的角度考虑,应当注意提交的信息以及规范。一个项目的提交信息首先得从下面三个方面达成一致:
具体的规则有下面7点:
第一条,如果内容和主题没有分开,git log --oneline
主题和下面的内容会一起显示。
第二条,主题超过50个字符时超过的部分在github上显示为...
,提交PR的时候超过的部分会被折断到comments中,很烦人。用vim去编辑提交信息的时候,如果看到主题的字的颜色变化了,就说明超过了50个字符。
第三、四条不评价,感觉更多是从美观和规范上统一的。
第五条,感觉这样可以少写一些字,而且和git 缺省的提交信息,如revert的提交信息一致。
1 2 3 |
|
第六条,因为git不会帮你wrap文字,所以得手动的来做这个事情,这里可以借助一些编辑器,如VI的帮助。 第七条,个人觉得这个才是最重要的,解释清楚修改的原因以及方式,引用别人文章里面的一个例子:
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 28 29 30 |
|
~/.gitmessage
这个template文件并且填入自定义模板:1 2 3 4 5 6 7 8 |
|
配置完成后在项目中做修改,git ci -a
就可以在模板的基础上修改了。
举一个项目中的一个例子,里面包含了业务需求的github issue的链接:
1 2 3 4 5 6 7 |
|
如果是用Pull Request方式工作的话,麻烦的地方在于修改的原因可能还得在comments里面再写一遍。解决的办法是创建issue/PR的template,参考这里。
什么,你问我为什么还没有被开除么? 因为领导不看提交 :)
]]>这是一个纯前端的项目,两年前前后端分离的时候的项目,Grunt workflow,测试框架使用Karma,用Phantomjs1.8.2
运行headless的测试,开发环境使用Chrome/Safari做功能性测试。开发环境基于node 0.12
,一些基础设施的更新,部署的脚本,smoke test是基于Ruby的,版本为2.0
。
这个前端的工程部署在两个不同的AWS Region的S3上,互为fail over,最前面有Akamai为它们做负载均衡。
持续集成的工具使用Bamboo,其agent需要有ruby 2.0
,node 0.12
,Phantomjs 1.8.2
的环境才可以运行具体的任务。整个过程已经做到了持续部署,一个完整的build过程如下:
太长时间没有人做技术上的升级,导致下面的一些隐患和问题:
1. 开发的工具版本落后,node当前版本已经是6.3
了,ruby 2.0的版本应该已经不维护了,同样,对应的Karma,Phantomjs都以及更新了很多
2. 运行build依赖的agent是共用的,如果有人对agent的环境进行修改,会影响该项目的持续集成
3. 未来需要将CI工具从Bamboo迁移到Buildkite,用pipeline as code的方式去构建,每个组自己去管理build agent,使用Docker会更加方便迁移
测试部分通过的过程及问题
1. 首先做的事情是构建一个基础的docker 镜像,包含最新的node 6.3.1
,phantomjs 2.1.1
,后来发现其实不用Phantomjs,这个有点多余了。 成果在这里: https://github.com/iambowen/node_on_docker%EF%BC%8C%E5%9B%A0%E4%B8%BA%E8%BF%99%E6%A0%B7%E7%9A%84%E7%8E%AF%E5%A2%83%E6%9B%B4%E5%8A%A0%E9%80%9A%E7%94%A8%E4%BA%9B%EF%BC%8C%E6%89%80%E4%BB%A5%E6%89%8Dpublish%E5%88%B0%E5%AE%98%E6%96%B9%E7%9A%84docker repository里面。
2. 在这个镜像的基础上,构建一个我们工程依赖环境的基础镜像,额外安装了Ruby 2.3
,最新的Chrome,git以及一些git的配置,因为需要从企业版github上pull代码。
3. 本地升级node版本,以及相关的grunt,karma,Phantomjs的版本,运行测试通过。
4. 将工程mount容器中,然后运行测试,npm install
失败,原因是安装fsevent
出错。查看了下这个包,原来只是给OSX下使用的。删除npm-shrinkwrap.json
后重新运行可以通过。原因是有人在OSX下运行了npm shrinkwrap
去生成的这个锁定版本的文件,真是烦人。于是反其道行之在容器里面生成npm-shrinkwrap.json
,在host上运行测试一切完好,就这样解决了这个问题。
5. 在Bamboo创建一个branch,然后针对我的分支代码运行测试
6. 测试里面的一个步骤是做bower install
安装第三方js类库,但是比较恶心的是,有些第三方类库是以git
的协议去下载,而不是https
。本地运行一切都好,但是在Bamboo Agent上运行的时候却出现了连接超时的问题,很有可能是Bamboo所在AWS的network ACL或者是security group没有允许9418
端口的TCP访问。不过最后解决的方式并不是修改防火墙或者将协议改为https
,而是直接把类库checking到git中,这样对应的修改Gruntfile,不用再运行bower install
。check in之后在Bamboo上运行还是失败,本地却可以通过,仔细检查,原来是一部分bower module目录名为dist
被git ignore掉了。
通过测试后,接下来就是部署了。部署要解决的问题是,如何让容器拿到AWS role的动态权限去做文件的上传更新操作。ECS好像是支持容器去assume role的操作,但是我们没有用ECS,所以只能考虑其它方式。
我想到的方式在bamboo 的 docker agent上 assume role
,拿到对应的credential后,将其作为环境变量传入到容器中。实验证明这样的方式是可行的,万幸bamboo的docker agent支持aws cli命令,不过没有jq
稍微增大了点提取credential的难度,脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
因为部署是用aws node 的sdk,所以读取的环境变量名字不太一样,要稍微注意下。
在CI上运行后,staging部署通过,手动在bamboo的docker agent上测试下是否能assume产品环境的部署的role,结果可以,那就是说产品环境的部署应该也可以通过了。
npm
sucks,更糟糕的是程序员在引入依赖的时候缺乏考虑,我在package.json
里面见到了不少无人维护的component,后续的升级维护是一个问题,联想以前的ruby项目也是一样。一旦有版本升级,碰到无人维护的gem时会非常痛苦。1 2 |
|
囧,staging的IP range也是172.17
,原来是这个原因。
于是,先停止这个网络设备,然后删除,之后再重启网络服务解决问题。
1 2 3 |
|
我觉得从这个错误中可以学到两个事情
Pet
服务器的不可靠性,如果服务器是每天都按照配置重新创建也不会出现这样的问题Cloudformation 是AWS的一项用来管理AWS相关的资源以及对资源的部署以及更新的服务。它具有以下几个特点:
/etc/resolv.conf
不会对应的修改,导致域名解析失败。
解决的办法是将DNS解析的任务交给虚拟机管理工具如virtualbox,假设我们要修改名为test
的虚拟机的设置:
1 2 3 4 5 6 7 |
|
重新启动vm,不管怎么切换网络,应该都不会再出现域名解析的问题。 如果是用Vagrantfile管理虚拟机的配置,可以更改vm的配置:
1 2 3 |
|
1
|
|
通过日志查找的方式去发现具体的问题可能会很耗时,因为需要等待akamai把日志上传。Akamai自己提供了解码错误代码的工具和API,具体的用法如下:
这个比较容易,从Luna Control Center
选择Resolve
=> Diagnostic Tools
。在Service Debugging Tools
部分选择Error Translator (Reference#)
,然后在Error String:
的input中输入错误码的字符串,点击Analyze
,等待一会就可以看到详细的错误信息以及原因。
docker run -it akamaiopen/api-kickstart /bin/bash
既可。Luna Control Center
选择CONFIGURE
=> Manage APIs
进入Open API 管理页面。在Luna APIs
下面添加新的collection,然后在该collection添加新的client,就可以拿到新的tokens,点击右上角的导出按钮,就可以将其导出到一个文本文件中,如名为api-kickstart.txt
的文件。python gen_edgerc.py -s default -f api-kickstart.txt
, 它会在用户根目录生成~/.edgerc
的credential文件。通过python verify_creds.py
可以验证credential的有效性。.edgerc
文件中的token其实也就是api请求时authorization的headers。.edgerc
文件设置验证完成后,可以使用python diagnostic_tools.py
来测试,它实际请求的API endpoint是/diagnostic-tools/v1/locations
和/diagnostic-tools/v1/dig
,返回如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Akamai的diagnostic API的列表在这里。ErrorCode解释的endpoint是/diagnostic-tools/v1/errortranslator{?errorCode}
,通过重用例子中的python代码即可发起这样的请求,比如把diagnostic_tools.py
修改如下(我就是这么懒):
1 2 3 |
|
之后就可以看到错误的原因是ERR_FWD_SSL_HANDSHAKE|err_conn_strict_cert
,也就是说我没有在CDN设置正确的certificate,导致它和origin的ssl handshake失败了。
如果没有什么特殊的需求,akamai web console中的diagnostic tool就可以满足需求,逼格较高或者有自动化需求的可以从命令行调用API输出错误原因。
]]>KMS是AWS提供的中心化的key托管服务,它使用硬件安全模块 (HSM)保护密钥安全。它可以被集成到其它的AWS服务中,如S3, EBS, RDS等,同时所有关于key的使用都会在CloudTrail中记录,以方便审计。
基本上来自于文档,其好处有如下几点:
可以看到的好处有很多,比如直接把加密过后的private key或者密码扔到repo中,再也不用担心被别人拿去干坏事。
要使用KMS服务,首先得创建一个新的master key。key是按照region划分, 自己创建key的价格是1刀一个月,每个月的前20000次请求是免费的。
在AWS Console -> IAM界面的Encryption Keys
中找到创建Key和Key管理的选项,如key的Enable
、Disable
或者删除等。当然,我们可以通过AWS CLI来创建key,这样可以将整个过程用代码管理起来:
123456789
,指定key policy并保存到文件(e.g policy.json
)中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 28 |
|
1
|
|
返回的内容可能如下
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
1
|
|
1
|
|
1
|
|
这种使用KMS的方式只能加密最多4KB的数据。想要加密更大的数据可以使用KMS去生成一个Data Key,然后利用Data Key去加密数据。
在REA项目中,在AWS上部署的大多数APP都是(尽量)遵循12factors原则的。应用运行时依赖的配置是通过user-data
传入环境变量设置。在一个instance上启动服务的过程大致如下:
1. 在launchConfiguration
中为instance添加instanceProfile
,对应的role有使用KMS的权限;
2. 在user-data
中设置cypher text并且解密到环境变量中:
1 2 3 4 5 6 7 8 9 10 11 |
|
屁屁踢如下:
Demo的环境是在本地实现的,repo在这里:https://github.com/iambowen/ansible-mesos
看不到屁屁踢的,请设置DNS为8.8.8.8或者翻墙……
]]>这些任务的工具,都是用ruby的aws-sdk
包装的,从表面上看,这么做有如下的好处:
对于程序员来说,这么做感觉棒棒哒,写完很有满足感。但是实际中会带来很大的问题,具体表现在维护方面。 我举两个例子:
最近,我和同事就碰到了这样的问题。我们AMI构建和部署的工具是用Ruby的aws-sdk
实现,我们要做的工
作是把构建AMI和部署的任务从一个AWS Account移到另外一个Account,原本的验证方式是通过Hard
Code的Credentials如AWS_SECRET_ACCESS_KEY/AWS_ACCESS_KEY_ID
。更好的实践是通过STS服务
,用AssumeRole的方式去获得临时的Credential。听上去并没有什么太大的难度,但是当我们去迁移的时
候,发现始终提示权限不足,而仔细检查role的权限后发现没有任何不妥,于是百思不得其解,尝试追踪
stacktrace也没什么结果。
无意中看了眼Gemfile
,感觉aws-sdk
的版本有点低,随手升了个级,然后试了下,竟然可以通过验证
了……。
多花了两个小时,就是因为没有再去维护这个工具。而这个工具实际实现的功能,用aws-cli
也可
以很容易实现,而且依赖更少:
aws-cli
(可能还有python,除了windows,一般的系统默认都会有)和bash就可
以了aws-cli
在镜像启动时自动更新,这样就完全不需要维护所以,如果大家要针对AWS做一些开发,比如镜像构建,清理或者自动化部署,推荐使用CLI的方式,而不是 SDK去实现,从使用的角度,依赖更少,从维护的角度,成本更低。
]]>于是,就有很多人(包括我)在堡垒机上生成key/pair, 而且private key很少加密(包括我),这个存在 很严重的安全风险。
一个比较合理的方式是通过ssh proxy的方式去访问目标服务器,这样不需要把key暴露给bastion,比如:
1
|
|
然后再启动一个新的ssh进程去通过proxy连接:
1
|
|
每次这么操作略麻烦,可以通过在ssh配置文件简化:
1 2 3 4 |
|
那么建立proxy就只是ssh user@bastion
就可以了,然后同理去ssh -p 9999 user@0
。
这么做的坏处在于~/.ssh/config
配置可能会迅速膨胀,同时,每次还是启动两个进程去完成这件事情,不开心。
于是,我们的安全大神介绍一个更加简单的方法,在~/.ssh/config
中,加入下面的内容:
1 2 |
|
如此我就可以通过ssh user@bastion/target
的方式直接ssh到远程主机,ProxyCommand
指令会
生成两个进程,后台proxy进程,前台的进程直接通过proxy连接到目标主机。这样从命令行窗口看来我只
是打开了一个会话。同时,你可以链接很多个主机,如ssh user@bastion/targetA/targetB/targetC
。
依次通过前一个主机建立的proxy连接到后面的主机上。
这个方法有一些局限:
targetC
退出到 targetB
…… (我想的)为了解决这些问题,大神想出了终极解决方案:
1 2 3 4 |
|
Host */*
: 匹配ssh到A/B/X
这样的主机类型,然后递归的ssh到链中的主机;ControlMaster auto
: 这个指令的意思是指ssh应当复用已有的control channel连接远程主
机,如果这样的channel不存在,则重新创建,以便以后的链接复用;ControlPath ~/.ssh/.sessions/%r@%h:%p
: 这个指令告诉ssh control channel socket
文件的位置。对于每个远程主机,socket文件应该是唯一的,如此我们可以重用已有连接并且跳过验证。所
以我们用%r
(remote login name),%h
(remote host name)和%p
(端口)作为文件名的部分。
唯一的问题是因为路径中的/
,这里会在%h
被当成一个目录,但是ssh不会自动创建目录;ProxyCommand blah
: 命令开始时就先创建了所有必须的目录。 ControlPersist
的意思是如果
control channel 2分钟内没有活动则停止ssh进程。如果你有两个会话bastion/HostA
和
bastion/HostB
,如果不配置ControlPersist
,结束第一个进程时第二进程也会同时被干掉。所以,当你用上面的配置去ssh user@bastion/A/B/C
时:
*/*
模式~/.ssh/.sessions/user@bastion/A/B/C:22
的socket,如果成功则建立连接,
没有则继续执行ProxyCommand
中的内容, 创建目录同时递归的ssh到最终的主机C~/.ssh/.sessions/user@bastion/A/B/C:22
的
control channel socket文件,并且成为control channel的master你现在有没有和我一样晕,在和大神交流一番后,大神告诉我一个改进版的配置:
1 2 3 4 5 6 |
|
这个配置要简单些,不过他假设你已经创建了~/.ssh/.sessions
目录。
荣耀归于Dmitry大神,虽然那个ssh keypair我还没有删除……。
]]>ssh -i key user@instance
)到EC2的instance,就会提示输入passphrase
passphrase
这个问题很有趣,我先查看了下key,在我的印象里,如果在ssh-keygen
的时候加入密码保护了,private
key 中会有如下的额外信息:
1 2 3 4 |
|
但是同事给我的private key中没有.
在不同的系统下尝试用该key去ssh到EC2 instance得到的结果都是需要输入passpharse,通过输入冗余
ssh信息ssh -vvvv
也没有看到什么有用信息(其实是我忽略了)。
google搜索,猜测会不会是key generate时候的格式不同导致的,但是觉得这种可能性不高。
在客户的ops channel询问了下,有人给出了这个建议,用
1
|
|
去检查key的完整性,返回结果如下:
1 2 3 |
|
实际上这个信息已经比较明显了,另外有人也从ssh debug的信息中指出:
1 2 |
|
private key的开始或者结束的marker出问题了,于是客户询问这个key是不是从slack拷贝过去的,因为
聊天工具有时候会自动纠错,把结束的marker ----
自动改成——
,他曾经就遇到过这种情况。
再看一遍private key,果然是这样……好羞愧。修改后,果然可以顺利ssh 到instance上了。
(更正下,虽然他指出了问题的来源,但是这段debug信息,在private key是完整的情况下仍然存在,所以这不是key出错的绝对证据。)
从这个事情中,我们可以得到一些教训
]]>除此之外,一些基础设施,比如企业私有的Repository(如Nexus, Koji, rubygems服务器等)出现问题,也会影响到整个开发和持续交付的时间。
如何解决这个问题?很简单,提高这些环境的可用性,把他们当做产品环境一样看待,提高出错的响应速度, 减少平均恢复时间等。
先举一个CI环境当做产品环境来对待的例子。 一些简单的背景:
其具体的结构图如下:
该结构详细解释如下:
cfn-hup
服务,监听Agent的Stack变化,如果有Metadata的变化,
比如,更新了Agent上支持的Java版本,则在Agent上更新该配置回顾这套结构,如果某个Agent挂掉,AutoScalingGroup会重新spin up一个新的Agent Instance。 如果Bamboo Master或者Master DB挂掉,也可以通过CloudFormation Stack以及备份的Snapshot 在1-2个小时以内恢复,时间的开销相对较少。
仔细的同学可能会注意到,为了满足运行build的各种条件,需要安装各种依赖,比如不同的Ruby版本, 不同的Java版本等,重新创建一个Agent Instance到配置完成注册成为Bamboo服务,时间会比较长。而且 如果Metadata的更新导致环境失败,会迅速影响到所有的Agent。
相信很多人会想到更好的解决方案,比如将每个build任务都在Docker容器中运行,如此作为整个CI环境 的维护者,只需要保证每个Agent上面有docker deamon运行,整个Agent挂掉的几率大大降低,同时维护 的责任分散到每个团队内,减轻了维护的压力。
下面介绍如何提高企业内部的私有Repository,如Nexus的可用性和稳定性以及快速恢复能力。 我们的Nexus服务器的结构图,如下:
详细解释如下:
对于上面的Nexus结构,由于有足够的备份,不论是Volume挂载失败需要恢复或者是Instance当机,处理的 时间成本都会比较低,在半个小时以内。
开发/测试依赖的环境可能还有很多,更多的把它们当做产品环境对待,会大大增加持续交付的流畅度,减轻环境维护方面的痛楚。
]]>我们使用AWS作为基础设施平台,通过EC2 AMI作为镜像部署,构建AMI工具基于Packer,加上了一些定制 的东西以减少配置,简化使用的方式。
Packer在构建AMI时,需要创建临时的key pair,security group以及EC2实例,因为需要对应的API credentials,像这样:
1 2 |
|
所以Packer需要在配置文件中hard code credentials或者通过环境变量传入,如果这些都没有找到, Packer会通过如下的步骤去自动查找credential:
1.查找AWS相关环境变量,如:
1 2 |
|
2.寻找AWS配置文件~/.aws/credentials
或者AWS_PROFILE
环境变量
3.查找运行Packer的EC2实例中instance profile中的role,然后用AWS STS(Security Token Service)来获取临时的credential.该role的policy至少要允许如下的Actions:
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 28 29 30 |
|
这几种方式中,使用instance profile的的方式是最好的,原因大致如下:
之所以总结Packer在构建AMI时指定credential的方式,是因为最近在把一个用packer构建AMI的流水线 从一个Region移到另一个Region时遇到了原有的credential不工作的情况,后来发现,其实运行这个任务 的Agent(EC2实例)初始化时就绑定了instance profile,不用设置任何额外的credential,packer可以 自动去读取,于是在移除了原有的各种credential之后,顺利的构建出了镜像。
]]>有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
每行是用空格分开的两个整数,第一个是 n, 第二个是 m ( 0 < m,n <=300)。最后一行是:
1
|
|
对于每行输入数据(最后一行除外),输出数据也是一行,即最后猴王的编号
1 2 3 4 |
|
1 2 3 |
|
有用类似链表的实现方法,我没有这么做……
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
比较简单,就是在netty的代码库中抄一些例子就可以了.要注意的有下面几点:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
PORT
随机端口的环境变量,否则前面的代理服务器无法绑定到后端的netty.
我个人觉得这是一个非常坑爹的设定,同时在官方的文档中没有特别说明。很容易引发下面的错误:1
|
|
解决的办法就是这样
1
|
|
Procfile
中加入以下内容:1
|
|
system.properties
加入:1
|
|
用mvn clean install
打包到target目录下
,然后运行heroku local web
,测试应用在本地是否工作正常。
heroku create
命令新建一个应用。git push heroku master
。heroku ps:scale web=1
保证至少有一个实例在运行这个应用。heroku open
可以打开部署后的服务页面。可能是因为java水平太差,感觉在heroku上部署java应用比部署ruby应用要难一些。部署完成后,发现还是不 能重现bug,感觉好受伤。
]]>客户的tech lead和我一起追踪这个问题,出问题的是一个微服务,其架构如下:
1
|
|
当时我的猜测是ELB和instance之间的连接或者处理出了什么问题,但是Tech lead先查看了ELB Cloudwatch 上的错误数量,并不多,Splunk中搜索ELB access log发生的频率也不高,当然从newrelic可以看到应用本 身的rpm也不是很高。
他先做的事情是在产品环境稳定重现bug,手段是通过一个超大header的Get请求,之后再接若干个请求,就可以 复现。CTO认为可能是Akamai和AWS中间发生了什么错误,于是我们查看了Akamai的access log,寻找504, 数量和ELB上出现的错误基本一致,排除是Akamai出问题的可能。
在这个过程当中,他新建了一个github issue,并将分析过程,以及检验的证据comment到issue中,很不错的 实践。
于是问题又回到了ELB和instance之间,我在猜测是不是因为ELB的cross zone负载均衡出了问题,ELB和instance 之间的网络访问出了错,不过回头想想感觉不太可能。查看了instance上access log,发现没有任何5xx的错误 代码记录。
tech lead再次查看了下Splunk,发现在某个时间段后,504的错误突然大幅增加,并且数量、频率比较稳定。
查看CI以及提交记录,发现刚好是一次大的重构的开始时间,将代码改为Freemonad风格,同时引入了unfilterd-netty-server 0.8.4
但是理论上来说重构没有功能性修改,而且也很难判断哪些代码会引起问题。
于是我将staging的版本回退到重构前,测试竟然是好的。。。damn it。
我突然想起来,ELB有个选项,idle connection timeout,大概意思就是说ELB和instance上app间如果 有连接空闲,超过一定时间后才关闭,这是为了减少http通信的开销。很有可能第一次的请求处理后,因为某种原因, 复用的连接没有正常的被Netty服务。所以我们测试了下,先发一次请求,成功,紧接着后面的请求理论上会失败, 因为它复用了前一个ELB和instance的连接,但如果这时再发一次请求,它应该也会成功,因为前一个空闲连接 被占用了。测试了下果然如此,并且当超时时间设置为1s时,基本不会出错了。
所以基本上可以认为是Netty处理大headers的问题,另外一个同事给出了这个netty issue的链接netty#3379,同时它给出了在本地测试的方法,就是用
curl去同时请求两个链接,这个时候它会复用连接,而不是关闭连接再重新打开。果然,unfilterd-netty-server
依赖的netty4.0.24.Final
是存在问题的,而比较新的4.0.29.Final
是没有问题的。
暴力覆写netty的版本可能会引入新的问题,不过我们试着冒下险,更改后所有测试都通过,并生成新的AMI,然后 在Staging部署测试,一切正常,最后部署产品环境,通知CTO,问题修复。
我们的做法比较粗暴,而且不是好的解决办法,最合适的方法是给unfilterd-netty-server
项目发pull
request升级它依赖的netty版本号。不过,花了好长时间也没有找到合适的公共netty站点去证实,加上开源
社区的反馈不一定会很快,所以还是选择了先暴力升级的方法。
鉴于还有其他项目使用这个类库,而它们都会存在相同的问题,所以必须在github中找到这些项目。幸好前一段 时间都在做patch management,通过一些插件可以将项目类库的依赖生成json文件并上传到s3 bucket。通过 查找这些依赖的json文件我很快就定位到了需要修改的系统并发了github issue,这些团队会自己决定如何处理 这个问题。
通过解决这次的bug,学到了很多追踪、分析和解决问题的方式,受益匪浅。
]]>