How SSH works?

简介:

SSH,全名secure shell,其目的是用来从终端与远程机器交互,SSH设计之处初,遵循了如下原则:

  • 机器之间通讯的内容必须经过加密。
  • 加密过程中,通过 public key加密,private 解密。

密钥:

SSH通讯的双方各自持有一个公钥私钥对,公钥对对方是可见的,私钥仅持有者可见,你可以通过”ssh-keygen”生成自己的公私钥,默认情况下,公私钥的存放路径如下:

  • 公钥:$HOME/.ssh/id_rsa.pub
  • 私钥:$HOME/.ssh/id_rsa

通讯原理:

前提条件:

  1. 两个节点都持有各自的公钥私钥对,分别标记为PUBLIC KEY(client), PRIVATE KEY(client), PUBLIC KEY(server), PRIVATE KEY(server)
  2. 服务器上运行了SSH服务程序

建立通信通道的步骤如下:

  1. 客户端发起请求给服务器,服务器发回自己的public key给客户端
  2. 客户端检查这个public key是否在自己的$HOME/.ssh/known_hosts中,如果没有,客户端会提示是否要把这个public key加入到known_hosts中。
  3. 客户端会提示输入密码,用户输入密码后,客户端会使用PUBLIC KEY(server)对密码加密,然后发送给服务器。
  4. 服务器收到密码后,使用PRIVATE KEY(server) 解密,校验密码正确性. ??? 需要解密吗?
  5. 客户端把PUBLIC KEY(client), 发送给服务器。
  6. 至此,通讯通道建立完毕,当客户端想服务器发送消息时,会使用PUBLIC KEY(server)加密,服务器会使用PRIVATE KEY(server)解密,当服务器向客户端发送消息时,会使用PUBLIC KEY(client)加密,客户端收到数据后,会使用PRIVATE KEY(client)解密。

免密码登录:

我们的目标是: 用户已经在主机A上登录为a用户,现在需要以不输入密码的方式以用户b登录到主机B上。
步骤如下:

  1. 以用户a登录到主机A上,生成一对公私钥。
  2. 把主机A的公钥复制到主机B的authorized_keys中,可能需要输入b@B的密码。

    ssh-copy-id -i ~/.ssh/id_dsa.pub b@B

  3. 在a@A上以免密码方式登录到b@B
ssh b@B

SSH forwarding:

ssh -f user@ssh_host -L 1433:target_server:1433 -N

Getting started with chef

Set up a ubuntu node and run recipe on it

Predication:

  1. a running ubuntu node which accessiable via SSH from your labtop(workstation)
  2. cd to chef-repo directory.

    Steps:


    Bootstrap ububtu:
knife bootstrap IP_ADDRESS -x USERNAME -P PASSWORD --sudo


Verify the installation completed
knife client list

you will see your nodename in the client list

Download cookbooks from community site
knife cookbook site install getting-started

the cookbook named “getting-started” will be downloaded into chef-repo/cookbooks/

Upload the recipe to Hosted Chef so it is available for our nodes
knife cookbook upload getting-started 


Add this new recipe to the new nodes run list
knife node run_list add NODENAME 'recipe[getting-started]'


Run the added recipe remotely via ssh
knife ssh name:NODENAME -x USERNAME -P PASSWORD "sudo chef-client" -a ipaddress


Runnig chef-client as a deamon
knife cookbook site install chef-client
knife cookbook upload chef-client
knife node run_list add NODENAME 'recipe[chef-client]'
knife ssh name:NODENAME -x USERNAME -P PASSWORD "sudo chef-client" -a ipaddress


Advanced tips:

  • add recipe when bootstrap node
knife bootstrap IP_ADDRESS -r 'recipe[chef-client]' -x USERNAME -P PASSWORD --sudo

Polymorphic Association

有这么一个需求,一个在线音乐商店系统,我们暂且叫它’online-store’,需要记录消费者对每首歌的评论,一个有经验的rails developer会很快地写出下面的代码:

class Music
  has_many :comments
end

class Comment
  belongs_to :music
end

对应的表结构如下:

#musics:   
  |  id  | integer|   
  | name | varchar|   
  | ...  |  ...   |   
#comments:   
  | id      | integer|   
  | text    | varchar|   
  | music_id| integer|   

如果需要给music添加,查看评论,可以通过如下代码实现:

  #添加评论
  music.comments.create {:text => 'this is realy nice'}
  #查看评论
  music.coments

风云变幻,斗转星移,apple的app store创造了软件销售的新模式,我们的vendor也坐不住了,决定在现有的音乐商店系统上出售应用程序,电影,游戏等内容,同样,这些内容也需要支持评论,有了前面成功的经验,你信心满满增加了下面几个model:

  class Application
    has_many :comments
  end

  class Movie
    has_many :comments
  end

  class Game
    has_many :comments
  end

再来看看我们之前写的model Comment:

class Comment
  belongs_to :music
end

现在需要支持多种内容,而且这些类内容之间出了都可以被评论外,再无其他关联,那这个belongs_to该怎么写呢?一个最直接的思路是,扩展Comment,让其支持对以上四类内容的评论,代码如下:

class Comment
  belongs_to :music, though => "music_id"
  belongs_to :game, though => "game_id"
  belongs_to :application, though => "application_id"
  belongs_to :movie, though => "movie_id"
end

表结构如下:

#comments:   
  | id             | integer|   
  | text           | varchar|   
  | music_id       | integer|   
  | game_id        | integer|
  | application_id | integer|
  | movie_id       | integer|

有了以上的model,你就可以给应用程序,电影,游戏增加评论了:

  #创建评论
  application.comments.create {:text => "this is a nice app"}
  movie.comments.create {:text => "this is a nice movie"}
  game.comments.create {:text => "this is a nice game"}
  #查看评论
  application.comments
  movie.comments
  game.comments

目前看来,这些代码工作得很好,然而,做为一个有着良好直觉的程序员,你敏锐地觉察到,将来可能有更多的内容出现在这个”onlne-store”中,也会有更多的内容需要支持评论——你成功地识别出一个”易变点“,每当新增一种内容的时候,你就需要打开这个Comment类,新增一个association,同时,还需要增加migration,这个设计明显违背了开闭原则”。那么,这个问题该怎么解决呢? 再来分析下这个问题,上面我们提到,这些model唯一的共性是 “可以被评论”,于是我们可以抽象出一个概念——“commentable”。如果我们让comment对象知道它所对应的”commentable”对象的id以及类型(game/application/movie),我们就可以获得一个“commentable”对象的所有comments,参考下面的代码:

  #查看id为1的music的评论
  Comment.find(:commentable_id => 1, :commentable_type => 'music')

  #查看id为1的application的评论
  Comment.find(:commentable_id => 1, :commentable_type => 'application')

如此,comments的表结构就可以简化为:

#comments:   
  | id              | integer|   
  | text            | varchar|   
  | commentable_id  | integer|   
  | commentable_type| varchar|

model代码简化为:

 #添加评论
 Comment.create({:text => "good staff", :commentable_id => "1", :commentable_type => "music"})
 #查看评论
 Comment.find(:commentable_id => 1, :commentable_type => 'music')
  class Comment
    belongs_to :commentable, :polymorphic => true
  end
  class Application
    has_many :comments, :as => :commentable
  end
  class Movie
    has_many :comments, :as => :commentable
  end
  class Game
    has_many :comments, :as => :commentable
  end
  #添加评论
  movie.comments.create {:text => 'this is realy nice'} 
  #查看评论
  movie.coments

更多关于“polymorphic association”的信息,请参考这里 怎么样,这样一来,再新增多少种内容类型,处理起来都非常容易,扩展下commentable_type的值就可以了。这个思路的根本出发点在于为识别、分离变化点。这类问题可以抽象为这样一个问题:”如何把一个概念应用到一组除了这个概念,没有其他任何关联的对象上?” ,此类问题可以采用上述思路解决。

声明:本文翻译自这里

递归101

我们都知道什么是递归吧?一个自己调用自己的函数,或者函数A调用函数B,函数B又调用函数A,或者是A调用B,B调用C, C调用A。但是我们大多数情况下所说的递归函数,是指一个调用自身的函数。
在Java世界中,递归函数的曝光率很低,说起来有不少原因。
第一,递归不直观,难以理解。对于一段循环代码(for, while), 你可以很直白地看到这段逻辑的全景,即便你是一个初学者,而对于一段递归代码,就不是那么容易了,你只能看到递归逻辑的一次调用,而不得不想象当递归调用发生时,这些多次调用是如何组合在一起的。
第二,比起递归,循环在java中更加容易实现,比如for ,for-each,while, do-while, 数组, iterator, ResultSet,这些结构都是用来实现循环的。
第三,Java中的递归有自己的Achille’s heel: call stack。

总的来说,当调用一个函数时,一个新的call stack frame会被放到call stack的顶部,用以保存局部变量,当前函数的caller,等等。但是,call stack的大小是有限的,当递归的深度不是很深时,调用递归函数是没有什么问题的,但是如果递归调用的深度无法预计,那么很有可能会导致stack overflow. 而循环却不会有这种问题(因为循环不会产生新的call stack frame),因此,使用循环更加安全。

Scala中的递归

Scala,作为一新兴的functional language,更偏爱递归胜过循环,那么在scala中,是如何解决call stack大小限制的问题的呢?我们来看一个例子:

  def listLength1(list: List[_]): Int = {
    if (list == Nil) 0
    else 1 + listLength1(list.tail)
  }

  var list1 = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  var list2 = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
  1 to 15 foreach( x => list2 = list2 ++ list2 )

  println( listLength1( list1 ) )
  println( listLength1( list2 ) )

函数listLenght1递归地计算list中item的数量,这个函数在计算item数量较少的list时工作地很好,然而在item数量很大时,就会得到stack overflow错误. 递归是函数式语言的谋生手段,但是在Scala中,递归仍然倒在了stack overflow的脚下。

先别急着放弃,Scala有一个很重要的优化递归的方案,只要你用了正确的递归类型。

首递归和尾递归

根据递归调用的方式,递归可以分为首递归和尾递归。在首递归函数中,函数调用自身后,再进行其他运算(可能会把自用自身后的结果做为这些运算的输入)。在尾递归函数中,所有的计算都在函数调用自身前完成,调用自身是尾递归函数中做的最后一件事情。

这两种递归的区别的重要性目前看起来还不是那么明显,然而,它确实很重要!想象一下一个尾递归函数的执行过程,首先完成所有的计算,在最后一步,马上进行对自身递归的调用,一般情况下,这个时候就该使用call stack frame记录方法调用状态了,然而,这里却不需要:我们不需要记录局部变量,因为所有的计算都已经完成。我们也不需要知道目前在哪个函数中,因为我们始终在同一个函数中。基于以上前提,scala不会创建一个新的call stack frame,而是重用当前的call stack frame ,无论调用次数有多少,call stack也不会增长。这就是scala中尾递归函数的特殊性。在其他语言中,语言设计者通过把尾递归转换成循环的方式进行了优化。

而在首递归函数中,递归调用是不一样的,这是为什么呢?想象一下一个首递归函数的执行过程:先执行一些计算,在递归调用自身,然后在执行另一些计算。在调用自身前,需要记住当前的局部变量,以便在从递归调用返回后继续进行后面的运算,这样,就必须创建一个新的call stack frame来记录当前状态。因此首递归函数还是会有stack overflow的风险。并且无法被优化。

在这里问你一个问题,上面的listLenght1是一个尾递归还是首递归?让我们来看着这个函数做了哪些事情。
A) 检查参数是否为Nil。
B) 如果为空,返回零,因为Nil的长度是零。
C) 如果不为空,则返回1加上递归调用的结果。
递归调用逻辑在这个函数的最后一行,应该是尾递归函数吧?错!在尾递归调用结束后,然后对递归调用结果加一,然后返回最终结果。这实际上首递归(或者可以叫做中递归)因为递归调用并不是所有运算的最后一步。

尾递归例子

当你用scala写一个递归函数时,你的目标是写成尾递归以便编译器对尾递归进行优化。现在让我们把上面的那个函数重写为尾递归函数。

def listLength2(list: List[_]): Int = {
  def listLength2Helper(list: List[_], len: Int): Int = {
    if (list == Nil) len
    else listLength2Helper(list.tail, len + 1)
  }
  listLength2Helper(list, 0)
}

println( listLength2( list1 ) )
println( listLength2( list2 ) )

我写成两个函数(listLength2 和一个内部的helper函数)以便和上面的例子中的函数接口保持一致。如果你能给listLength2Helper的参数给个默认值,我们就能只提供一个函数,但是我不知道怎么做。长话短说:listLength2只调用了做了实际工作的listLength2Helper,而且listLength2Helper也是个递归函数。

listLength2Helper是个尾递归函数吗?递归调用是所有运算的最后一步,允许scala进行优化?就像listLenght1一样,listLength2首先检查参数是否为Nil,如果不是,就进行递归调用,但是仍然会有一个加一的操作 —— len + 1。难道这个就不是尾递归吗?不,len + 1 运算会在递归调用前运算。只有所有的参数运算完了以后,才会进行递归调用,这个函数确实是个尾递归函数。

什么是vagrant?

看<a =href=”http://vagrantup.com/docs/getting-started/index.html”>这里</a>

为什么要用vagrant?

看<a =href=”http://vagrantup.com/docs/getting-started/why.html”>这里</a>

怎样开始使用vagrant?

  1. 安装VirtualBox
  2. 安装基础虚拟机
$ vagrant box add base http://files.vagrantup.com/lucid32.box

这个基础虚拟机是一个运行着ubuntu,没有安装额外软件的虚拟机,以后的所有更改都是在这个基础虚拟机上进行的。

由于国内网络环境不好,推荐先把这个基础虚拟机镜像下载到本地,然后通过如下脚本安装:

$ vagrant box add base ./lucid32.box

然后运行如下命令初始化,启动虚拟机:

$ vagrant init base
$ vagrant up

至此,一个vagrant的基础虚拟机环境就创建成功了,想知道如何访问这个虚拟机?通过如下命令就可以登陆上虚拟机了。

$ vagrant ssh

怎么配置虚拟机?

#创建一个目录用于存放配置虚拟机的文件
$ mkdir mybox 
#初始化这个新虚拟机
$ cd mybox 
$ vagrant box add mybox http://files.vagrantup.com/lucid32.box
$ vagrant init

经过以上步骤,在mybox目录下会多出一个文件Vagrantfile,这个就是配置虚拟机的文件, 在这个文件里,vagrant支持通过chefpuppet对虚拟机进行配置。

如何使用这个虚拟机?

# 启动虚拟机
$ vagrant up
# 停止虚拟机
$ vagrant halt
# 登陆到虚拟机
$ vagrant ssh
# 删除虚拟机
$ vagrant destroy
# 把当前虚拟机打包成一个本地镜像,便于再次分发
$ vagrant package