Bowen's Blog

Respect My Authorita.

Hand Over DNS Resolve to VirutalBox

| Comments

当你用vagrant新建一个虚拟机(driver 为virtualbox)并使用NAT方式让guest虚拟机连接外网时,如果有无线网络的变化,虚拟机中/etc/resolv.conf不会对应的修改,导致域名解析失败。

解决的办法是将DNS解析的任务交给虚拟机管理工具如virtualbox,假设我们要修改名为test的虚拟机的设置:

1
2
3
4
5
6
7
 ~> VBoxManage list vms
"mesos1" {74214693-3477-4386-a9b7-4abc3b7e608d}
.......
"test" {b269c98f-00e8-49a3-a8d0-53629187ea62}

#保证vm没有在运行,然后执行
 ~> VBoxManage modifyvm test  --natdnsproxy1 on

重新启动vm,不管怎么切换网络,应该都不会再出现域名解析的问题。 如果是用Vagrantfile管理虚拟机的配置,可以更改vm的配置:

1
2
3
config.vm.provider "virtualbox" do |v|
  v.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
end

Using Akamai Diagnostic tools/API

| Comments

有时候在Akamai上提交应用修改后,因为配置的问题,可能出现错误,像下面这样:

1
#30.657008d1.1452737568.1e40544

通过日志查找的方式去发现具体的问题可能会很耗时,因为需要等待akamai把日志上传。Akamai自己提供了解码错误代码的工具和API,具体的用法如下:

Lunar Control Centre 的 Diagnostic Tools


这个比较容易,从Luna Control Center选择Resolve => Diagnostic Tools。在Service Debugging Tools部分选择Error Translator (Reference#),然后在Error String:的input中输入错误码的字符串,点击Analyze,等待一会就可以看到详细的错误信息以及原因。

使用Akamai Diagnostic API


  1. Akamai提供了Sample Client去调用API,除了clone client的repo,还可以直接使用docker,直接运行docker run -it akamaiopen/api-kickstart /bin/bash既可。
  2. 生成新的client请求的token。首先在Luna Control Center选择CONFIGURE => Manage APIs进入Open API 管理页面。在Luna APIs下面添加新的collection,然后在该collection添加新的client,就可以拿到新的tokens,点击右上角的导出按钮,就可以将其导出到一个文本文件中,如名为api-kickstart.txt的文件。
  3. 在client端设置token。在client的目录下运行python gen_edgerc.py -s default -f api-kickstart.txt, 它会在用户根目录生成~/.edgerc的credential文件。通过python verify_creds.py 可以验证credential的有效性。.edgerc文件中的token其实也就是api请求时authorization的headers。
  4. 测试请求。.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
root@16119b2d4eb8:/opt/examples/python# python diagnostic_tools.py

Requesting locations that support the diagnostic-tools API.

There are 72 locations that can run dig in the Akamai Network
We will make our call from Adelaide, Australia

; <<>> DiG 9.8.1-P1 <<>> developer.akamai.com -t A
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12919
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 8, ADDITIONAL: 8

;; QUESTION SECTION:
;developer.akamai.com.        IN  A

;; ANSWER SECTION:
developer.akamai.com. 300 IN  CNAME   san-developer.akamai.com.edgekey.net.
san-developer.akamai.com.edgekey.net. 21600 IN CNAME e4777.dscx.akamaiedge.net.
e4777.dscx.akamaiedge.net. 20 IN  A   23.4.164.144

;; AUTHORITY SECTION:
dscx.akamaiedge.net.  4000    IN  NS  n6dscx.akamaiedge.net.
...............

Akamai的diagnostic API的列表在这里。ErrorCode解释的endpoint是/diagnostic-tools/v1/errortranslator{?errorCode},通过重用例子中的python代码即可发起这样的请求,比如把diagnostic_tools.py修改如下(我就是这么懒):

1
2
3
+ location_result = httpCaller.getResult('/diagnostic-tools/v1/errortranslator?errorCode=30.657008d1.1452737568.1e40544')
- location_result = httpCaller.getResult('/diagnostic-tools/v1/locations')
+ print location_result["errorTranslator"]["reasonForFailure"]

之后就可以看到错误的原因是ERR_FWD_SSL_HANDSHAKE&#x7c;err_conn_strict_cert,也就是说我没有在CDN设置正确的certificate,导致它和origin的ssl handshake失败了。

如果没有什么特殊的需求,akamai web console中的diagnostic tool就可以满足需求,逼格较高或者有自动化需求的可以从命令行调用API输出错误原因。

AWS KMS and Its Usage

| Comments

什么是KMS


KMS是AWS提供的中心化的key托管服务,它使用硬件安全模块 (HSM)保护密钥安全。它可以被集成到其它的AWS服务中,如S3, EBS, RDS等,同时所有关于key的使用都会在CloudTrail中记录,以方便审计。

KMS的优点


基本上来自于文档,其好处有如下几点:

  1. 中心化的key托管服务。举个例子,对于不同的环境(staging/production),我们需要维护不同private key 去做部署,调试等等,还得考虑定期rotate。出于安全考虑,这些private key不推荐和部署的repo放在一起。一般情况下你得把它们放在一个统一的地方去保存,如Rattic或者Vault去管理。这样的话,你的承担这些工具的维护任务。KMS可以让你免除维护的压力。
  2. 和 AWS 服务的集成。S3,EBS,RDS的数据加密,都可以使用KMS。同时,它也支持命令行或者API去管理key,进行key的rotate,加密解密等。
  3. 可伸缩性、耐用性和高可用性。KMS会自动帮你保存key多份拷贝,耐用性99.999999999%,同时KMS会在多个AZ部署,保证高可用性。
  4. 安全。KMS在服务端通过硬件加密,保证了你在上面存储的key的安全性。其实现的细节在这里
  5. 审计。对于key的请求,都会被记录在CloudTrail中,方便审计。

可以看到的好处有很多,比如直接把加密过后的private key或者密码扔到repo中,再也不用担心被别人拿去干坏事。

使用 KMS 服务


要使用KMS服务,首先得创建一个新的master key。key是按照region划分, 自己创建key的价格是1刀一个月,每个月的前20000次请求是免费的。

创建新key


在AWS Console -> IAM界面的Encryption Keys中找到创建Key和Key管理的选项,如key的EnableDisable或者删除等。当然,我们可以通过AWS CLI来创建key,这样可以将整个过程用代码管理起来:

  1. 假设AWS account为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
{
  "Id": "KeyPolicy-1",
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Allow access for Admin",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789:root"
      },
      "Action": [
        "kms:Create*",
        "kms:Describe*",
        "kms:Enable*",
        "kms:List*",
        "kms:Put*",
        "kms:Update*",
        "kms:Revoke*",
        "kms:Disable*",
        "kms:Get*",
        "kms:Delete*",
        "kms:ScheduleKeyDeletion",
        "kms:CancelKeyDeletion"
      ],
      "Resource": "*"
    }
  ]
}
  1. 创建key,并绑定对应的policy
1
 ~> aws kms create-key --key-usage "encryption key" --description "master key" --policy "$(cat policy.json)"

返回的内容可能如下

1
2
3
4
5
6
7
8
9
10
11
{
  "KeyMetadata": {
    "KeyId": "aabbccdd-4444-5555-6666-778899001122",
      "Description": "master key",
      "Enabled": true,
      "KeyUsage": "encryption key",
      "CreationDate": 2433401783.841,
      "Arn": "arn:aws:kms:ap-southeast-2:123456789:key/aabbccdd-4444-5555-6666-778899001122",
      "AWSAccountId": "123456789"
  }
}
  1. 授权IAM user/role去使用或者管理key,这是除了policy之外的另一种访问管理控制的机制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  "Sid": "Allow use of the key",
  "Effect": "Allow",
  "Principal": {"AWS": [
    "arn:aws:iam::111122223333:user/KMSUser",
    "arn:aws:iam::111122223333:role/KMSRole",
  ]},
  "Action": [
    "kms:Encrypt",
    "kms:Decrypt",
    "kms:ReEncrypt*",
    "kms:GenerateDataKey*",
    "kms:DescribeKey"
  ],
  "Resource": "*"
}
  1. 创建alias,可以作为keyid的替身使用
1
aws kms create-alias --alias-name "alias/test-encryption-key" --target-key-id aabbccdd-4444-5555-6666-778899001122
  1. 使用key去加密文件。加密后的输出为base64编码后的密文,可以进一步解码为二进制文件。
1
aws kms encrypt --key-id 1234abcd-12ab-34cd-56ef-1234567890ab --plaintext fileb://ExamplePlaintextFile --output text --query CiphertextBlob | base64 --decode > ExampleEncryptedFile
  1. 解密文件,原理如加密的过程。
1
aws kms decrypt --ciphertext-blob fileb://ExampleEncryptedFile --output text --query Plaintext | base64 --decode > ExamplePlaintextFile

局限性

这种使用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
cipher="CiBwo3lXT5T+pTZu7P9Cqkh0Iolpaz9FMzha5jJb6kTdiBKNAQEBAgB4cKN5V0+U/qU2buz/QqpIdCKJaWs/RTM4WuYyW+pE3YgAAABkMGIGCSqGSIb3DQEHBqBVMFMCAQAwTgYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAwIxkIN0TeX1HiWyj0CARCAIVaSfD/spTBFAfBVIp/Wy6TadlwUKKz/oTMWUUob9fcxdg=="
cipher_blob=$(mktemp /tmp/blob.123)
echo -n "${cipher}" | base64 -D > cipher_blob
PASSWORD=$(aws kms decrypt --ciphertext-blob fileb://$cipher_blob \
             --query "Plaintext"                         \
             --output text                               \
             --region ap-southeast-2  | \
             base64 -D
)

docker run -d -e PASSWORD=$PASSWORD .......

Stop Wrapping AWS SDK to Create Tools

| Comments

大概4-5年前,客户开始使用AWS作为他们的开发和测试环境,因为澳洲当时没有亚马逊的数据中心,所以只好 使用us-east, us-west这些region。后来澳洲有了AWS的数据中心,应用的产品就都迁移到新的region 和新的AWS账户下。由于这个历史原因,一部分bake AMI的任务以及部署的任务,都是跨账户以及region 的。

这些任务的工具,都是用ruby的aws-sdk包装的,从表面上看,这么做有如下的好处:

  1. 更细粒度去控制这些任务以及过程
  2. 代码可测试
  3. 打包/发布/共享会更加容易些
  4. 只要SDK支持,你都可以用自己熟悉的语言去实现这些工具
  5. 写代码实现感觉很牛逼

对于程序员来说,这么做感觉棒棒哒,写完很有满足感。但是实际中会带来很大的问题,具体表现在维护方面。 我举两个例子:

  1. 不是所有人都喜欢这个工具,有些人会提交patch,改进这个工具,有些人会重新实现一个类似功能的工 具,比如我喜欢用Java,但是现有的工具是用Ruby实现,我表示不服,重头写一个。维护的难度在这种分散 的项目和语言中增大了。
  2. 实现者没有在对工具进行维护,其中依赖的sdk已经过期,而作为工具的使用者,并没有察觉到这件事情, 在实际的使用中会遇到问题,面对非常深的stacktrace,debug的难度较高。

最近,我和同事就碰到了这样的问题。我们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也可 以很容易实现,而且依赖更少:

  1. 本地使用,只需要有aws-cli(可能还有python,除了windows,一般的系统默认都会有)和bash就可 以了
  2. CI的slave上使用,可以让aws-cli在镜像启动时自动更新,这样就完全不需要维护
  3. 如果cli参数有变动,提示会更加直接些,也容易追踪

所以,如果大家要针对AWS做一些开发,比如镜像构建,清理或者自动化部署,推荐使用CLI的方式,而不是 SDK去实现,从使用的角度,依赖更少,从维护的角度,成本更低。

更加安全和简单的方式通过堡垒机ssh

| Comments

理想情况下的运维,是不需要ops去ssh到服务器上检查问题(包括安全问题)/日志等,这些是可以通过更好 监控,如使用newrelic,或者更好的日志收集系统,如splunk等去避免。不过现实不总是完美的,加上历史 遗留的原因,ops总是会ssh到堡垒机(bastion host),然后跳转到目标服务器去做操作。

于是,就有很多人(包括我)在堡垒机上生成key/pair, 而且private key很少加密(包括我),这个存在 很严重的安全风险。

一个比较合理的方式是通过ssh proxy的方式去访问目标服务器,这样不需要把key暴露给bastion,比如:

1
 ~> ssh -L 3333:destination_host:22 user@bastion

然后再启动一个新的ssh进程去通过proxy连接:

1
 ~> ssh -p 3333 user@0

每次这么操作略麻烦,可以通过在ssh配置文件简化:

1
2
3
4
Host bastion
        HostName 192.168.1.1
        HostKeyAlias bastion
        LocalForward 9999 target:22

那么建立proxy就只是ssh user@bastion就可以了,然后同理去ssh -p 9999 user@0。 这么做的坏处在于~/.ssh/config配置可能会迅速膨胀,同时,每次还是启动两个进程去完成这件事情,不开心。

于是,我们的安全大神介绍一个更加简单的方法,在~/.ssh/config中,加入下面的内容:

1
2
Host */*
        ProxyCommand ssh $(dirname %h) -W $(basename %h):%

如此我就可以通过ssh user@bastion/target的方式直接ssh到远程主机,ProxyCommand指令会 生成两个进程,后台proxy进程,前台的进程直接通过proxy连接到目标主机。这样从命令行窗口看来我只 是打开了一个会话。同时,你可以链接很多个主机,如ssh user@bastion/targetA/targetB/targetC。 依次通过前一个主机建立的proxy连接到后面的主机上。

这个方法有一些局限:

  1. 不能在主机链上指定不同的端口;
  2. 不能对不同的主机使用不同的登录用户名;
  3. 不同链上建立的连接不能重用已经建立的连接,这可能会导致连接的速度减缓;
  4. 其实还有个问题就是不能很容易的从targetC退出到 targetB…… (我想的)

为了解决这些问题,大神想出了终极解决方案:

1
2
3
4
Host */*
    ControlMaster auto
    ControlPath   ~/.ssh/.sessions/%r@%h:%p
    ProxyCommand /bin/sh -c 'mkdir -p -m700 ~/.ssh/.sessions/"%r@$(dirname %h)" && exec ssh -o "ControlMaster auto" -o "ControlPath   ~/.ssh/.sessions/%r@$(dirname %h):%p" -o "ControlPersist 120s" -l %r -p %p $(dirname %h) -W $(basename %h):%p'
  1. Host */*: 匹配ssh到A/B/X这样的主机类型,然后递归的ssh到链中的主机;
  2. ControlMaster auto: 这个指令的意思是指ssh应当复用已有的control channel连接远程主 机,如果这样的channel不存在,则重新创建,以便以后的链接复用;
  3. ControlPath ~/.ssh/.sessions/%r@%h:%p: 这个指令告诉ssh control channel socket 文件的位置。对于每个远程主机,socket文件应该是唯一的,如此我们可以重用已有连接并且跳过验证。所 以我们用%r(remote login name),%h(remote host name)和%p(端口)作为文件名的部分。 唯一的问题是因为路径中的/,这里会在%h被当成一个目录,但是ssh不会自动创建目录;
  4. ProxyCommand blah: 命令开始时就先创建了所有必须的目录。 ControlPersist的意思是如果 control channel 2分钟内没有活动则停止ssh进程。如果你有两个会话bastion/HostAbastion/HostB,如果不配置ControlPersist,结束第一个进程时第二进程也会同时被干掉。

所以,当你用上面的配置去ssh user@bastion/A/B/C时:

  1. ssh 匹配到了*/*模式
  2. ssh 尝试重用~/.ssh/.sessions/user@bastion/A/B/C:22的socket,如果成功则建立连接, 没有则继续执行
  3. ssh执行ProxyCommand中的内容, 创建目录同时递归的ssh到最终的主机C
  4. 然后ssh在主机C上进行身份验证,成功则创建~/.ssh/.sessions/user@bastion/A/B/C:22的 control channel socket文件,并且成为control channel的master
  5. 显示命令行提示符

你现在有没有和我一样晕,在和大神交流一番后,大神告诉我一个改进版的配置:

1
2
3
4
5
6
Host */*
        ControlMaster auto
        ProxyCommand    /usr/bin/ssh -o "ControlMaster auto" -o "ControlPath ~/.ssh/.sessions/%%C" -o "ControlPersist 120s" -l %r -p %p $(dirname %h) -W $(basename %h):%p

Host *
        ControlPath     ~/.ssh/.sessions/%C

这个配置要简单些,不过他假设你已经创建了~/.ssh/.sessions目录。

荣耀归于Dmitry大神,虽然那个ssh keypair我还没有删除……。

Don't Copy/paste Keys From Chatting Tools

| Comments

今天,别的组的同事过来问我一个关于SSH的问题,问题是这样的:

  1. 客户把AWS的ssh instance的private key通过slack拷给了同事;
  2. 同事发现用部署工具fabric可以使用该key,ssh到EC2的instance上进行部署;
  3. 但是如果使用key去ssh(如ssh -i key user@instance)到EC2的instance,就会提示输入passphrase
  4. 客户的Ops很肯定说这个private key没有加passphrase

这个问题很有趣,我先查看了下key,在我的印象里,如果在ssh-keygen的时候加入密码保护了,private key 中会有如下的额外信息:

1
2
3
4
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,B88893260B6CCFDC6304101075B74A9F
.....

但是同事给我的private key中没有.

在不同的系统下尝试用该key去ssh到EC2 instance得到的结果都是需要输入passpharse,通过输入冗余 ssh信息ssh -vvvv也没有看到什么有用信息(其实是我忽略了)。

google搜索,猜测会不会是key generate时候的格式不同导致的,但是觉得这种可能性不高。

在客户的ops channel询问了下,有人给出了这个建议,用

1
openssl rsa -text -noout -in KEYFILE

去检查key的完整性,返回结果如下:

1
2
3
[vagrant@localhost ~]$ openssl rsa -text -noout -in id_rsa
unable to load Private Key
140516793460640:error:0906D066:PEM routines:PEM_read_bio:bad end line:pem_lib.c:802:

实际上这个信息已经比较明显了,另外有人也从ssh debug的信息中指出:

1
2
debug1: key_parse_private2: missing begin marker
debug1: key_parse_private_pem: PEM_read_PrivateKey failed

private key的开始或者结束的marker出问题了,于是客户询问这个key是不是从slack拷贝过去的,因为 聊天工具有时候会自动纠错,把结束的marker ----自动改成——,他曾经就遇到过这种情况。 再看一遍private key,果然是这样……好羞愧。修改后,果然可以顺利ssh 到instance上了。 (更正下,虽然他指出了问题的来源,但是这段debug信息,在private key是完整的情况下仍然存在,所以这不是key出错的绝对证据。)

从这个事情中,我们可以得到一些教训

  1. 不要用聊天工具copy/paste private key或者代码之类的东西,很容易引起错误。
  2. 同时,这种方式也很不安全,尽量不要这么做,要么在传递完后迅速删除聊天记录
  3. 或者使用一个公共的key管理的服务,如rattic,或者使用临时生成的credential来ssh,如这个项目进一步提高安全性。

Make Everything Production Like - (2/2)

| Comments

开发环境出问题的时候,影响到只是自己,如果持续集成环境或者其相关的基础设施出了问题,那影响到的就 是所有人以及整个开发的进展,我们曾经遇到一次这样的事故,整个 Bamboo(CI)环境的Master和Database都被干掉了,出乎意料的是AWS RDS的自动镜像同时也被删除,于是所有的人花了一个礼拜才重新建好了全部的流水线。

除此之外,一些基础设施,比如企业私有的Repository(如Nexus, Koji, rubygems服务器等)出现问题,也会影响到整个开发和持续交付的时间。

如何解决这个问题?很简单,提高这些环境的可用性,把他们当做产品环境一样看待,提高出错的响应速度, 减少平均恢复时间等。

先举一个CI环境当做产品环境来对待的例子。 一些简单的背景:

  1. 客户使用的持续集成工具是Bamboo
  2. CI Master,Agent以及数据库服务都采用了AWS的服务,如EC2、RDS、R53等
  3. 用CloudFormation去管理整个CI服务的基础设施,同时用Rake task去简化管理的难度。

其具体的结构图如下: arch

该结构详细解释如下:

  1. Bamboo Agent和 Bamboo Master的依赖及其配置打包成RPM,部署的EC2 instance基于Centos定制过的AMI
  2. Bamboo Master/Agent/DB 都用CloudFormation管理
  3. 在Bamboo Agent Stack的LaunchConfiguration中的Metadata中,安装在Agent中运行各种build的依赖, 比如不同的Ruby版本等,同时定义cfn-hup服务,监听Agent的Stack变化,如果有Metadata的变化, 比如,更新了Agent上支持的Java版本,则在Agent上更新该配置
  4. Bamboo Agent由一个AutoScalingGroup管理,除了自动Scale,还可以每天定时启动或者停止Agent Instance,节省成本
  5. Bamboo Master的Stack中做的事情类似
  6. Bamboo Master的SecurityGroup只接受来自Bamboo Agent的SecurityGroup的访问,Bamboo Master DB的SecurityGroup只接受来自Bamboo Master SecurityGroup的请求
  7. Bamboo Master DB使用RDS服务
  8. Bamboo Master服务器上运行的Cron Job每天会定时备份文件系统的Snapshot
  9. Bamboo 服务器上的一个Plan每天会运行定时的任务,创建Master DB的Snapshot,RDS可以设置自动 生成snapshot,不过一旦Master DB被干掉,snapshot也会被一起干掉。所以,安全期间,还是manual snapshot比较好。

回顾这套结构,如果某个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 arch

详细解释如下:

  1. Nexus服务运行在ELB后的一个EC2 Instance上
  2. 其部署基于安装有Nexus服务的Base AMI以及CloudFormation stack
  3. Nexus的artifact目录挂载在一个EBS volume下,Instance在初始化时配置了InstanceProfile, 在crontab添加脚本,可以用InstanceProfile中的role去创建EBS volume的daily snapshot,以防止artifact数据丢失
  4. 监控方面,如果ELB下面的健康的Instance数量少于1或者Instance上的EBS Volume没有正确的挂载,都会触发Cloudwatch Alarm,并通过SNS通知Pagerduty,然后Pagerduty再将警报发给维护Nexus的Ops

对于上面的Nexus结构,由于有足够的备份,不论是Volume挂载失败需要恢复或者是Instance当机,处理的 时间成本都会比较低,在半个小时以内。

开发/测试依赖的环境可能还有很多,更多的把它们当做产品环境对待,会大大增加持续交付的流畅度,减轻环境维护方面的痛楚。

Specifying Amazon Credentials in Packer

| Comments

Packer是一个用一份配置构建跨平台镜像的工具,它支持EC2 AMI,Vmware, QEMU,Virtualbox,docker等多个平台。

我们使用AWS作为基础设施平台,通过EC2 AMI作为镜像部署,构建AMI工具基于Packer,加上了一些定制 的东西以减少配置,简化使用的方式。

Packer在构建AMI时,需要创建临时的key pair,security group以及EC2实例,因为需要对应的API credentials,像这样:

1
2
access key id:     AKIAIOSFODNN7EXAMPLE
secret access key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

所以Packer需要在配置文件中hard code credentials或者通过环境变量传入,如果这些都没有找到, Packer会通过如下的步骤去自动查找credential:

1.查找AWS相关环境变量,如:

1
2
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

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
{
  "Statement": [{
      "Effect": "Allow",
      "Action" : [
        "ec2:AttachVolume",
        "ec2:CreateVolume",
        "ec2:DeleteVolume",
        "ec2:CreateKeypair",
        "ec2:DeleteKeypair",
        "ec2:CreateSecurityGroup",
        "ec2:DeleteSecurityGroup",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CreateImage",
        "ec2:RunInstances",
        "ec2:TerminateInstances",
        "ec2:StopInstances",
        "ec2:DescribeVolumes",
        "ec2:DetachVolume",
        "ec2:DescribeInstances",
        "ec2:CreateSnapshot",
        "ec2:DeleteSnapshot",
        "ec2:DescribeSnapshots",
        "ec2:DescribeImages",
        "ec2:RegisterImage",
        "ec2:CreateTags",
        "ec2:ModifyImageAttribute"
      ],
      "Resource" : "*"
  }]
}

这几种方式中,使用instance profile的的方式是最好的,原因大致如下:

  1. 固定credential的方式会带来安全隐患,一旦泄露,会导致巨大的损失。
  2. 从安全的角度考虑,API credential需要rotate,这样会带来维护成本。读取配置文件也有同样的 问题。
  3. 在持续集成流水线AMI构建任务中,credential必须通过环境变量的方式传入,任务本身增加了额外的 依赖,维护成本提高。

之所以总结Packer在构建AMI时指定credential的方式,是因为最近在把一个用packer构建AMI的流水线 从一个Region移到另一个Region时遇到了原有的credential不工作的情况,后来发现,其实运行这个任务 的Agent(EC2实例)初始化时就绑定了instance profile,不用设置任何额外的credential,packer可以 自动去读取,于是在移除了原有的各种credential之后,顺利的构建出了镜像。

Solution for Joseph Question

| Comments

囧瑟夫问题:

描述

有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。

输入

每行是用空格分开的两个整数,第一个是 n, 第二个是 m ( 0 < m,n <=300)。最后一行是:

1
0 0

输出

对于每行输入数据(最后一行除外),输出数据也是一行,即最后猴王的编号

样例输入

1
2
3
4
6 2
12 4
8 3
0 0

样例输出

1
2
3
5
1
7

有用类似链表的实现方法,我没有这么做……

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
//for joseph problem
#include <iostream>
using namespace std;

int joseph(int n, int m){
  int flat[300];
  for(int i = 0; i < n; i++)
    flat[i] = 1;

  for(int i = 0, count = n, mod = m - 1; count != 1; i = ((++i) % n)){
      if(flat[i] == 1){
        if(mod == 0){
          flat[i] = 0;
          count--;
        }
        mod = (mod - 1) % m;
      }
  }
  for(int i = 0; i < n; i++){
    if(flat[i] == 1)
      return i + 1;
  }
}

int main(){
  int set_n[100], set_m[100];
  int count = 0;

  while(true){
    int n, m;
    cin >> n >> m;
    if ((n == 0) && (m == 0)){
      break;
    } else {
      set_n[count] = n;
      set_m[count] = m;
      count++;
    }
  }

  for(int i = 0; i < count; i++)
    cout << joseph(set_n[i], set_m[i]) << endl;

  return 0;
}