Hello Vagrant

Hello Vagrant

回想以前,想要安装个虚拟机是多么的麻烦。先要费尽心机找到想要的操作系统镜像文件,然后安装虚拟化软件,按照其提供的GUI界面操作一步步创建,整个过程费时费力。但是,自从使用了Vagrant以后,咱腰不酸了,腿不痛了,一口气起5个虚拟机还不费劲。

Vagrant是什么?

这是官网上Vagrant的介绍。

Create and Configure lightweight, reproducible, and portable development environments.

即用来创建和配置轻量级、可重现的、便携式的开发环境。

使用Vagrant可以将创建虚拟机的整个过程自动化起来,并具有高度的重用性。假如你是个开发者,你可以很容易为每个团队成员创建一模一样的开发环境,从根本上防止‘在我的机器上可以工作’之类的bug。假如你是个测试人员,可以一键创建多个一模一样的测试环境并行跑测试,并且跑完测试后还可以一键销毁这些测试环境,达到真正的按需创建。如果你是devops成员,需要和AWS、Chef之类的工具打交道,那么Vagrant是个很好的结合点。你可以通过Vagrant在AWS上直接创建虚拟机,并且自动运行Chef的脚本配置你的新虚拟机。

几个概念

正式介绍Vagrant功能之前先了解一下Vagrant使用的一些概念。

  • Provider - 供应商,在这里指Vagrant调用的虚拟化工具。Vagrant本身并没有能力创建虚拟机,它是调用一些虚拟化工具来创建,如VirtualBox,VMWare,甚至AWS。

  • Box - 可被Vagrant直接使用的虚拟机镜像文件。针对不同的Provider,Box文件的格式是不一样的。

  • Vagrantfile - Vagrant根据Vagrantfile中的配置来创建虚拟机。在Vagrantfile文件中你需要指明使用哪个Box,需要预安装哪些软件,虚拟机的网络配置等。

Vagrant的安装

安装Vagrant非常简单,可以在Downloads页面选择最新的版本安装。Vagrant支持Windows、Linux、Mac等平台。

Box管理

使用Vagrant之前先要给Vagrant添加Box,也就是可供Vagrant使用的虚拟机镜像文件。Vagrant官网本身维护了一些镜像文件,我们可以直接使用。http://www.vagrantbox.es/上面有更多的box可以供我们使用。

1
2
3
4
5
6
#添加名为precise32的box文件  $ vagrant init precise32 http://files.vagrantup.com/precise32.box $ vagrant box list precise32 (virtualbox) $ vagrant box remove precise64 virtualbox 

可以看到Box与Provider是相关的,每个Box都必须指定Provier,只有使用对应的Provier才能正确使用Box。

创建并运行虚拟机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ vagrant box list precise32 (virtualbox) $ vagrant init precise32 A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on `vagrantup.com` for more information on using Vagrant. $ vagrant up Bringing machine 'default' up with 'virtualbox' provider... [default] Importing base box 'precise32'... [default] Matching MAC address for NAT networking... [default] Setting the name of the VM... [default] Clearing any previously set forwarded ports... [default] Creating shared folders metadata... [default] Clearing any previously set network interfaces... [default] Preparing network interfaces based on configuration... [default] Forwarding ports... [default] -- 22 => 2222 (adapter 1) [default] Booting VM... [default] Waiting for machine to boot. This may take a few minutes... [default] Machine booted and ready! [default] Mounting shared folders... [default] -- /vagrant 

vagrant init precise32会在当前目录下生成一个Vagrantfie文件,其使用precise32作为box。vagrant up则是使用virtual box这个provider来初始化并启动precise32这个虚拟机。

我们可以详细的看看Vagrantfile这个文件。

Vagrantfile
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API及语法版本 VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|  # 使用的box  config.vm.box = "precise32"  # Create a forwarded port mapping which allows access to a specific port  # within the machine from a port on the host machine. In the example below,  # accessing "localhost:8080" will access port 80 on the guest machine.  # config.vm.network :forwarded_port, guest: 80, host: 8080  # Create a private network, which allows host-only access to the machine  # using a specific IP.  # config.vm.network :private_network, ip: "192.168.33.10"  # Create a public network, which generally matched to bridged network.  # Bridged networks make the machine appear as another physical device on  # your network.  # config.vm.network :public_network  # If true, then any SSH connections made will enable agent forwarding.  # Default value: false  # config.ssh.forward_agent = true  # Share an additional folder to the guest VM. The first argument is  # the path on the host to the actual folder. The second argument is  # the path on the guest to mount the folder. And the optional third  # argument is a set of non-required options.  # config.vm.synced_folder "../data", "/vagrant_data"  # Provider-specific configuration so you can fine-tune various  # backing providers for Vagrant. These expose provider-specific options.  # Example for VirtualBox:  #  # config.vm.provider :virtualbox do |vb|  # # Don't boot with headless mode  # vb.gui = true  #  # # Use VBoxManage to customize the VM. For example to change memory:  # vb.customize ["modifyvm", :id, "--memory", "1024"]  # end  #  # View the documentation for the provider you're using for more  # information on available options.  # Enable provisioning with Puppet stand alone. Puppet manifests  # are contained in a directory path relative to this Vagrantfile.  # You will need to create the manifests directory and a manifest in  # the file precise32.pp in the manifests_path directory.  #  # An example Puppet manifest to provision the message of the day:  #  # # group { "puppet":  # # ensure => "present",  # # }  # #  # # File { owner => 0, group => 0, mode => 0644 }  # #  # # file { '/etc/motd':  # # content => "Welcome to your Vagrant-built virtual machine!  # # Managed by Puppet.\n"  # # }  #  # config.vm.provision :puppet do |puppet|  # puppet.manifests_path = "manifests"  # puppet.manifest_file = "site.pp"  # end  # Enable provisioning with chef solo, specifying a cookbooks path, roles  # path, and data_bags path (all relative to this Vagrantfile), and adding  # some recipes and/or roles.  #  # config.vm.provision :chef_solo do |chef|  # chef.cookbooks_path = "../my-recipes/cookbooks"  # chef.roles_path = "../my-recipes/roles"  # chef.data_bags_path = "../my-recipes/data_bags"  # chef.add_recipe "mysql"  # chef.add_role "web"  #  # # You may also specify custom JSON attributes:  # chef.json = { :mysql_password => "foo" }  # end  # Enable provisioning with chef server, specifying the chef server URL,  # and the path to the validation key (relative to this Vagrantfile).  #  # The Opscode Platform uses HTTPS. Substitute your organization for  # ORGNAME in the URL and validation key.  #  # If you have your own Chef Server, use the appropriate URL, which may be  # HTTP instead of HTTPS depending on your configuration. Also change the  # validation key to validation.pem.  #  # config.vm.provision :chef_client do |chef|  # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"  # chef.validation_key_path = "ORGNAME-validator.pem"  # end  #  # If you're using the Opscode platform, your validator client is  # ORGNAME-validator, replacing ORGNAME with your organization name.  #  # If you have your own Chef Server, the default validation client name is  # chef-validator, unless you changed the configuration.  #  # chef.validation_client_name = "ORGNAME-validator" end 

从上述的文件可以看出Vagrantfile可以配置很多东西,比如使用的Box,需要转发的端口,同步指定的目录,使用Chef、puppet等对虚拟机进行预配置等。

如果修改了Vagrantfile中的配置,只需要执行vagrant reload来应用新配置。

同步目录

虚拟机启动起来以后就可以ssh上去了。

1
2
3
4
5
6
7
8
9
10
11
$ vagrant ssh Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic-pae i686)  * Documentation: https://help.ubuntu.com/ Welcome to your Vagrant-built virtual machine. Last login: Wed Oct 2 09:41:08 2013 from 10.0.2.2 vagrant@precise32:~$ who vagrant pts/0 2013-10-02 09:47 (10.0.2.2) vagrant@precise32:~$ hostname precise32 vagrant@precise32:~$ 

Vagrant会自动给虚拟机根目录下创建一个名为vagrant的目录。这个目录可以与主机Vagrantfile所在的目录保持同步。这个同步是相互的,无论改动了主机目录中的文件,还是虚拟机目录中的文件,都可以自动同步到另一方。

1
2
3
4
5
6
7
8
9
vagrant@precise32:~$ cd /vagrant/ vagrant@precise32:/vagrant$ ls Vagrantfile vagrant@precise32:/vagrant$ touch test.txt vagrant@precise32:/vagrant$ exit logout Connection to 127.0.0.1 closed. $ ls Vagrantfile test.txt 

多机器管理

其实Vagrantfile支持配置多台机器,如果你需要设置多台服务器及数据库环境,可以用一个Vagrantfile搞定。

Vagrantfile
1
2
Vagrant.configure("2") do |config| config.vm.provision "shell", inline: "echo Hello" config.vm.define "web" do |web| web.vm.box = "apache" end config.vm.define "db" do |db| db.vm.box = "mysql" end end 

这个文件配置了两个box,一个叫web,一个叫db。现在启动虚拟机就需要加上虚拟机名了。

1
2
3
4
5
6
7
8
#启动web虚拟机 $ vagrant up web #启用db虚拟机 $ vagrant up db #默认启动所有的虚拟机 $ vagrant up 

关闭虚拟机

Vagrant提供了好几种方法来关闭虚拟机,你可以根据不同的情况选择不同的方式。

vagrant suspend将虚拟机置于休眠状态。这时候主机会保存虚拟机的当前状态。再用vagrant up启动虚拟机时能够返回之前工作的状态。这种方式优点是休眠和启动速度都很快,只有几秒钟。缺点是需要额外的磁盘空间来存储当前状态。

vagrant halt则是关机。如果想再次启动还是使用vagrant up命令,不过需要多花些时间。

vagrant destroy则会将虚拟机从磁盘中删除。如果想重新创建还是使用vagrant up命令。

另外1.2以上版本的Vagrant还引用了插件机制。可以通过vagrant plugin来添加各种各样的plugin,这给Vagrant的应用带来了更大的灵活性和针对性。比如可以添加vagrant-windows的插件来增加对windows系统的支持,通过添加vagrant-aws插件来实现给AWS创建虚拟机的功能。你也可以编写自己的插件。由于Vagrant是ruby写的一个gem,其插件的编写也是使用的Ruby语言。这里就不多做介绍了。感兴趣的可以去官网查看。

作者: 黄博文 @无敌北瓜 
出处: http://www.cnblogs.com/huang0925
黄博文的地盘
本文版权归本人和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 
分类:  编程开发

前文详细介绍了Façade模式、Adapter模式、Strategy模式和Bridge模式,并进行了比较。本文将再介绍四个常用的设计模式:Abstract Factory(抽象工厂)模式、Decorator(装饰)模式、Observer(观察者)模式、Template Method(模板)模式。开始介绍之前,笔者想说一下自己经常纠结的一个问题,什么时候开始考虑应用模式?作为一个新手,刚接触设计模式时都会为之倾倒,自觉醍醐灌顶,非常的迫切将所学的设计模式应用于实践,所以在编程的初期就会考虑各种模式的应用。个人的经验告诉我,对业务的理解是一个渐进明细的过程,开发的初期往往很难看到整个业务需求的全貌和本质,此时强行应用设计模式往往会导致Over Engineering。笔者认为应用设计模式的最佳时机往往是在代码重构的时候,彼时你已掌握业务的本质,对模式的应用往往能够恰到好处,其实这也是JIT的思想。

一.Abstract Factory模式

1. 目标需要为特定的客户(或情况)提供对象组,组内之间的对象处于一个上下文环境。

2. 问题需要实例化一组相关的对象,并从对象创建逻辑层面保证对象之间正确的关系,例如:在游戏设计中,在非洲大陆,不能够创建出美洲豹对象。

3. 解决方案协调对象组的创建。提供一种方式,将如何执行对象实例化的规则从使用这些对象的客户对象中提取出来,实现对象创建和对象使用的解耦。

4. 参与者和协作者Abstract Factory为如何创建对象组的每个成员定义公共接口。特定的ConcreteFactory创建其所对应的具体对象组。

5. 效果:抽象工厂模式将“使用那些对象”的规则与“如何使用这些对象”的逻辑进行解耦,实现关注点分离。

6. 实现定义一个抽象类来指定创建哪些对象。然后为每个组实现一个具体类。UML图如下:

clip_image002

二.Decorator模式

1. 目的:在运行时,实现动态的为每一个对象添加职责。

2. 问题:要使用的对象将执行所需要的基本功能。但是,这些基本功能确定以后,可能需要为这个对象添加一些其他的附加功能,并且对于不同的情况可能添加的附加功能的种类和数量都是不确定的。

3. 解决方案:通过添加装饰类,而不是扩展子类,在运行时为基本类对象扩充功能。抽象的说:有一个基本功能,还有些可选功能,每一个具体的对象,在基本功能的基础上通过选用不同的可选功能来定制。基本功能作为:ConcreteComponent;可选功能作为:ConcreteDecorator。

4. 参与者和协作者ConcreteComponent让Decorator对象为自己添加功能。有时候用ConcreteComponent的派生类提供核心功能,在这种情况下ConcreteComponent不再是具体的,而是抽象的。Component类定义了所有这些类的接口。

5. 效果:附加功能放在Decorator对象中。好处是可以在ConcreteComponent对象的功能之前或之后添加功能(取决于Decorator类的具体实现),但是对象链总是终于ConcreteComponent对象。

6. 实现:创建一个抽象类来表示原类和要添加到这个类的功能。在装饰类中,将对新功能的调用放在对紧随其后对象的调用之前或之后,以获得正确的顺序。调用方式为new ConcreteDecorator(new ConcreteComponent())。UML图如下:

image

三.Observer模式

1. 目标:解开观察者和主体之间的耦合,二者之间的关系在运行时动态建立,实现订阅--发布模型(Pub-Sub Model).

2. 问题:对象间经常存在这样一种关系:某个对象状态的改变将导致另一些对象的状态变化。或者说,有些对象作为观察者在始终盯着某个对象,一旦有事发生就需要立即行动。抽象的说:当主体发生变化时,不同的观察者做出不同的行动,而且他们的行动是自我控制的,无需主体发出指令。

3. 解决方案:通过将主体和观察者分开,而不是简单的将观察者的行为硬编码到主体类中,观察者通过监测主体类的实践实现自我控制。

4. 参与者和协作者Subject由三个函数构成:Attach(添加新的观察者)、Detach(删除过时的观察者)、Notify(依次更新观测者的信息);Observer定时更新自己的状态,根据主体的变化,做出相应的反应。

5. 效果:观察者对主体而言是透明的,观察者可以自发的做出反应。

6. 实现:创建一个Subject类,包含有三个函数。还有一个ConcreteSubject类获得主体的状态信息。创建一个Observer的抽象基类,还有不同观测者的ConcreteObserver类,定时更新自己的信息。UML图如下:

clip_image006

四.Template Method模式

1. 目标定义业务逻辑中算法的骨架,将一些步骤推迟到子类中实现。可以不改变算法的结构而重新实现这个算法中的某些步骤。

2. 问题要完成在某一细节层次一致的一个过程或一系列步骤,但其中个别步骤在更细节的层次上有不同的实现。

3. 解决方案允许定义可变的子步骤,同时保持基本步骤和步骤之间的执行顺序不变。

4. 参与者和协作者Abstract Class,该类中有一个定义了一般算法的方法(Template Method)这个方法调用了代表算法中各步骤的方法(Primitive Operation)。Concrete Class,实现Primitive Operation以完成算法中与特定子类相关的步骤。

5. 效果提供了一个很好的代码复用平台,并且从结构上减少了算法出错的概率。同时,提供了一种机制,使得约束、规则可以一次编码,处处使用。对于具体类的实现者而言,无需关注整个算法,只需关注该具体类中特殊的实现逻辑即可。

6. 实现创建一个抽象类,用抽象方法实现一个过程。这些抽象方法必须在子类中实现,以执行过程的每个步骤。如果这些步骤是独立变化的,那么每个步骤都可以是Strategy模式来实现(很多情况下,模式都不是独立使用,而是组合使用)。UML图如下:

clip_image008

【版权所有,转载请注明文章出处: Maxwell Zhou
 
.NET技术
 
如何编写优雅的代码:06. 设计模式应用案例(上)
摘要: 前文一共介绍了四人帮(Gang of Four)总结的的11个设计模式,对初学者而言,光看文字描述和UML类图略显抽象。本着Learning in Doing的原则,本文将举一些实际的业务需求场景,以C#代码为例,讲述在编程的过程中如何应用设计模式,实现模块间低耦合,高内聚,编写出优雅的代码。需说明的是,接下来的例子相对简单,省略了业务逻辑代码,目的是为了让大家专注于设计模式的应用,忽略业务逻辑本身的复杂性,毕竟本文的目的是加深对设计模式本身的理解。 阅读全文
posted @  2013-10-07 12:27 Maxwell Zhou 阅读(383) |  评论 (0)  编辑
 
如何编写优雅的代码:05. 设计模式(下)
摘要: 截至上一篇文章,已经介绍了8个常用的设计模式。四人帮(Gang of Four)总结的第一版设计模式中,一共有23个模式,后来又陆续总结了些,应该有三十多个。个人实际工作中常用到11个设计模式(前文介绍的8个以及本文将介绍的3个),我认为这也是最常用的模式。本文将再介绍3个常用的设计模式:Singleton(单体)模式、Factory Method(工厂方法)模式和Prototype(原型)模式。在下一篇将介绍部分设计模式的具体应用。 阅读全文
posted @  2013-10-05 17:46 Maxwell Zhou 阅读(535) |  评论 (2)  编辑
 
如何编写优雅的代码:04. 设计模式(中)
摘要: 前文详细介绍了Façade模式、Adapter模式、Strategy模式和Bridge模式,并进行了比较。本文将再介绍四个常用的设计模式:Abstract Factory(抽象工厂)模式、Decorator(装饰)模式、Observer(观察者)模式、Template Method(模板)模式。开始介绍之前,笔者想说一下自己经常纠结的一个问题,什么时候开始考虑应用模式?作为一个新手,刚接触设计模式时都会为之倾倒,自觉醍醐灌顶,非常的迫切将所学的设计模式应用于实践,所以在编程的初期就会考虑各种模式的应用。个人的经验告诉我,对业务的理解是一个渐进明细的过程,开发的初期往往很难看到整个业务需求的全貌和本质,此时强行应用设计模式往往会导致Over Engineering。笔者认为应用设计模式的最佳时机往往是在代码重构的时候,彼时你已掌握业务的本质,对模式的应用往往能够恰到好处,其实这也是JIT的思想。 阅读全文
posted @  2013-10-02 22:57 Maxwell Zhou 阅读(652) |  评论 (0)  编辑
 
如何编写优雅的代码:03. 设计模式(上)
摘要: 前文介绍了设计结构优良的代码应该遵循的设计原则和尽量避免的设计臭气。基于这些原则,四人帮(Gang of Four)抽象出一些常用的设计模式,用于解决在编程过程中反复遇到的设计问题。笔者认为“复用”是软件设计最具价值的理念之一,甚至有人提出软件即复用。通常我们认为复用是指代码层面的复用,然而笔者认为设计模式是设计思想的重用。本文将重点介绍常用的四个模式:Façade模式、Adapter模式、Strategy模式和Bridge模式。考虑到部分模式比较抽象难懂,笔者将在后面专门写一遍设计模式应用案例的文章。 阅读全文
posted @  2013-09-21 13:09 Maxwell Zhou 阅读(227) |  评论 (0)  编辑
 
如何编写优雅的代码:02. 设计原则
摘要: 前文介绍了优雅代码的三个标准:风格好、结构好和性能好的三好代码。对于风格好有很多的代码规范供参考,都是编程习惯的问题,养成就好。但对于结构好这种“内功”则需要一定时间的修炼,理论与实践的互相印证,不断的思辨优秀的设计思想,才能游刃有余地将一些优秀的设计原则应用到实际软件开发中。本文,笔者将重点介绍:设计的由来、糟糕的设计给软件带来的隐患以及优秀的设计应该遵循的五大原则。 阅读全文
posted @  2013-09-15 12:17 Maxwell Zhou 阅读(205) |  评论 (0)  编辑
 
如何编写优雅的代码:01. 概述
摘要: 笔者认为所谓优雅的代码包含三部分涵义:风格好、结构好和性能好的三好代码。不仅仅是C#语言,所有的编程语言都适用,只不过每种语言的特性和语法不同,实践三好代码的方式和途径也不尽相同。需要注意的是,在有些时候这三种内涵之间是矛盾的。例如,结构好的代码,有时候却会导致性能的下降。遇到这种情况,也不必纠结,在各种利益之间做好权衡,保证全局利益的最大化才是王道。本文以C#语言为例,总结C#编程实践中,如何做到代码的优雅。 阅读全文
posted @  2013-09-08 21:54 Maxwell Zhou 阅读(127) |  评论 (0)  编辑
 
.Net平台互操作技术:03. 技术验证
摘要: 上面两篇文章分别介绍了.Net平台互操作技术面临的问题,并重点介绍了通过P/Invoke调用Native C++类库的技术实现。光说不做是假把式,本文笔者将设计实验来证明P/Invoke调用技术的可行性。 阅读全文
posted @  2013-08-17 16:50 Maxwell Zhou 阅读(76) |  评论 (0)  编辑
 
.Net平台互操作技术:02. 技术介绍
摘要: 上一篇文章简单介绍了.Net平台互操作技术的面临的主要问题,以及主要的解决方案。本文将重点介绍使用相对较多的P/Invoke技术的实现:C#通过P/Invoke调用Native C++ Dll技术、C#调用Native C++代码示例、非托管内存的释放和平台调用性能提升技巧。 阅读全文
posted @  2013-08-17 16:21 Maxwell Zhou 阅读(41) |  评论 (0)  编辑
 
.Net平台互操作技术:01. 主要问题
摘要: 在.Net平台进行程序设计时,经常遇到基于Native C++,已经开发出很多类库,而现在需要用C#语言调用Native C++类库。这种情况在金融公司的量化投资部门经常发生。原因是,金融业务系统的对计算性能极其敏感,所以很多计算模块是用Native C++实现;然而用户操作界面用一般采用C#开发,理由很简单,C#开发效率确实胜过C++。本文围绕C#语言调用C++代码存在问题进行讨论。 阅读全文
posted @  2013-08-17 15:48 Maxwell Zhou 阅读(123) |  评论 (0)  编辑
 
C# 邮件服务总结
摘要: 在企业级应用中,用户经常需要掌握当前业务流转的状态。为满足该业务需求,通常的解决方案是,在业务流程的关键节点处设置系统提醒服务,如:手机短信提醒和邮件提醒等。大型企业一般都会在企业内部部署企业级的邮件服务器,所以邮件提醒服务较手机短信提醒会更加经济和安全。本文将总结,在企业级业务系统中,如何使用.Net的邮件服务接口,实现邮件提醒功能。 阅读全文
posted @  2013-06-12 19:45 Maxwell Zhou 阅读(427) |  评论 (0)  编辑
 
C#性能优化方法和实践
摘要: 关于C#性能优化的介绍有很多资料,如《Effective C#》、《More Effective C#》以及互联网上的技术文档都有介绍,对这方面内容感兴趣的程序员推荐系统地阅读。本文,我主要介绍工作实践中对C#性能优化的体会、认识和实践。 阅读全文
posted @  2013-06-01 18:26 Maxwell Zhou 阅读(438) |  评论 (0)  编辑
 
基于Asp.net MVC的系统架构
摘要: 2009开始接触.Net平台的相关技术,近年来也一直在用Asp.net MVC搭建一些业务系统。今天,我将介绍系统搭建的过程中用到的技术,工具和思想,抛砖引玉! 阅读全文
posted @  2013-03-02 13:19 Maxwell Zhou 阅读(122) |  评论 (0)  编辑
分类:  软件设计.NET技术

你可能感兴趣的:(vagrant)