熟练使用vagrant(10):vagrant批量创建虚拟机

官方手册已经给出了批量创建虚拟机的用法:Multi-Machine

config.vm.define可以定义虚拟机在vagrant中的name标识符。例如:

1
2
3
4
Vagrant.configure('2') do |config|
config.vm.box = "generic/ubuntu2004"
config.vm.define "ubuntu2004_junmajinlong"
end

如果为config.vm.define指定了语句块,则这个语句块相当于在config内部创建了一个独立的作用域更小的config环境。通过这种方式,可以在一个Vagrantfile中同时定义多个虚拟机。

例如,官方手册给的例子:

1
2
3
4
5
6
7
8
9
10
11
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

在上面示例中,web和db两个语句块参数和config语句块参数具有相同的功能,不同的仅仅是它们的作用域不同,config在整个Vagrant.configure do...end语句块中生效,而web或db只在config.vm.define do...end语句块中生效。

因此,能定义在config下的属性,也都能定义在config.vm.define的语句块内。例如:

1
2
3
4
5
6
7
8
9
10
11
12
Vagrant.configure("2") do |config|
config.vm.provision :shell, inline: "echo A"

config.vm.define :test1 do |test|
test.vm.provision :shell, inline: "echo B"
end
config.vm.define :test2 do |test|
test.vm.provision :shell, inline: "echo D"
end

config.vm.provision :shell, inline: "echo C"
end

需注意,不同作用域内定义重复的属性(例如上面的provision),外层优先级高于内层,同层则按顺序。因此,对于上面的示例中,将先输出A,再输出C,最后输出B或D。

当在Vagrantfile中通过config.vm.define定义了多个虚拟机时,某些控制虚拟机的vagrant子命令的语义将发生变化,例如:

  • vagrant ssh需要指定名称或ID表明要连接到哪个虚拟机
  • vagrant up默认将启动Vagrantfile中定义的所有虚拟机,如果想要只启动某个或某些虚拟机,需指定名称或ID或匹配名称的正则表达式
  • vagrant halt、suspend、reload等也都类似于up子命令

例如,某Vagrantfile中定义了leader、follower0、follower1、follower2共四台虚拟机:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 连接到leader这台虚拟机
vagrant ssh leader

# 启动这四台虚拟机
vagrant up

# 关闭follower1这台虚拟机
vagrant halt follower1

# 重启所有follower虚拟机
# 当vagrant发现目标参数包含在双斜线//内,
# 将使用正则去匹配目标
vagrant reload /follower\d/

对于像vagrant ssh这样需要指定单个目标虚拟机的子命令,如果确实想要省略单个目标,可将某个虚拟机定义为默认虚拟机。最多只能定义一个默认虚拟机。

1
2
3
config.vm.define "web", primary: true do |web|
# ...
end

默认情况下,vagrant up会启动Vagrantfile中定义的所有虚拟机,但可以通过参数autostart来控制某虚拟机是否自动在vagrant up时自动启动,该参数默认值为true。

例如:

1
2
3
config.vm.define "web"
config.vm.define "db"
config.vm.define "db_follower", autostart: false

这表示执行vagrant up时,将启动web和db,不会启动db_follower,只有vagrant up db_follower时才会启动db_follower。

借助ruby的迭代功能,可以方便地定义多个虚拟机。例如使用each(注意不要使用for循环,因为for每次迭代时都引用同一个迭代变量,而each等方法式迭代语句块的迭代变量在每次迭代过程中都会重新创建)。

1
2
3
4
5
6
(1..3).each do |i|
config.vm.define "node-#{i}" do |node|
node.vm.provision "shell",
inline: "echo hello node #{i}"
end
end

使用vagrant批量创建LNMP所需虚拟机

下面是我使用vagrant自动安装LNMP所需虚拟机的Vagrantfile文件。

该示例总共安装5个虚拟机,它们都是centos7系统:

  • 一个nginx节点
  • 一个php节点
  • 3个mysql节点,一个准备做主从同步的主,另外两个准备做从节点

同时还做了一点点基本的配置:

  • 所有节点都配置了国内的yum源和epel源
  • nginx节点安装nginx
  • php节点配置remi和remisafe源,并安装php和php-fpm
  • 三个mysql节点都配置了mysql源,并安装了mysql和mysql-server
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
Vagrant.configure("2") do |config|
############## yum repos variable ##############
base_repo = <<~BASE
[base]
name=CentOS-$releasever - Base
baseurl=https://mirrors.ustc.edu.cn/centos/$releasever/os/$basearch/
gpgcheck=0

[updates]
name=CentOS-$releasever - Updates
baseurl=https://mirrors.ustc.edu.cn/centos/$releasever/updates/$basearch/
gpgcheck=0

[extras]
name=CentOS-$releasever - Extras
baseurl=https://mirrors.ustc.edu.cn/centos/$releasever/extras/$basearch/
gpgcheck=0
BASE

epel_repo =<<~EPEL
[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
baseurl=https://mirrors.ustc.edu.cn/epel/7/$basearch
enabled=1
gpgcheck=0
EPEL

php_repo = <<~PHP
[remi]
name=remirepo
baseurl=http://mirrors.ustc.edu.cn/remi/enterprise/7/php74/x86_64/
enable=1
gpgcheck=0

[remisafe]
name=remisaferepo
baseurl=http://mirrors.ustc.edu.cn/remi/enterprise/7/safe/x86_64/
enable=1
gpgcheck=0
PHP

mysql_repo = <<~MYSQL
[mysql57]
name=MySQL
baseurl=https://mirrors.cloud.tencent.com/mysql/yum/mysql57-community-el7/
enabled=1
gpgcheck=0
MYSQL

########## base repo and epel repo for all vm ###########
config.vm.provision "shell", inline: <<-SHELL
sudo bash
mkdir /etc/yum.repos.d/bak
mv /etc/yum.repos.d/CentOS-*.repo /etc/yum.repos.d/bak
# yum base repo
cat <<'EOF' >/etc/yum.repos.d/base.repo
#{base_repo}
EOF
# yum epel repo
cat <<'EOF' >/etc/yum.repos.d/epel.repo
#{epel_repo}
EOF
SHELL

# all vm: centos7
config.vm.box = "generic/centos7"

######### nginx node ##########
config.vm.define "nginx" do |ngx|
ngx.vm.hostname = "nginx-vm"
ngx.vm.provider :virtualbox do |vb|
vb.name = "nginx_centos7"
end

ngx.vm.provider :hyperv do |v|
v.vmname = "nginx_centos7"
end

ngx.vm.provision "shell", inline: "sudo yum install -y nginx"
end

######### php node ##########
config.vm.define "php" do |php|
php.vm.hostname = "php-vm"
php.vm.provider :virtualbox do |vb|
vb.name = "php_centos7"
end

php.vm.provider :hyperv do |v|
v.vmname = "php_centos7"
end

php.vm.provision "shell", inline: <<-SHELL
sudo bash
cat <<'END' >/etc/yum.repos.d/remi.repo
#{php_repo}
END
yum install -y php php-fpm
SHELL
end

########### mysql master ###########
config.vm.define "mysql-master" do |mysql|
mysql.vm.hostname = "mysql-master-vm"
mysql.vm.provider "virtualbox" do |vb|
vb.name = "mysql-master_centos7"
end

mysql.vm.provider :hyperv do |v|
v.vmname = "mysql-master_centos7"
end

mysql.vm.provision "shell", inline: <<-SHELL
sudo bash
cat <<'END' >/etc/yum.repos.d/mysql.repo
#{mysql_repo}
END
yum install -y mysql-community-{server,client}
SHELL
end

########## mysql slaves ############
2.times do |i|
config.vm.define "mysql-slave-#{i}" do |mysql|
mysql.vm.hostname = "mysql-slave-#{i}-vm"

mysql.vm.provider :virtualbox do |vb|
vb.name = "mysql-slave-#{i}_centos7"
end

mysql.vm.provider :hyperv do |v|
v.vmname = "mysql-slave-#{i}_centos7"
end

mysql.vm.provision "shell", inline: <<-SHELL
sudo bash
cat <<'END' >/etc/yum.repos.d/mysql.repo
#{mysql_repo}
END
yum install -y mysql-community-{server,client}
SHELL
end
end
end

然后启动即可:

1
2
3
4
5
# virtualbox
$ vagrant up

# hyper-v
$ vagrant up --provider hyperv

如果想要并行启动(virtualbox不支持并行启动):

1
$ vagrant up --parallel

如果发现mysql节点错误想要重新启动mysql并让它们执行provision:

1
$ vagrant up /mysql.*/ --provision

删除所有虚拟机:

1
$ vagrant destroy -f