有没有可能在Vagrant完成所有虚拟机的配置后在虚拟机上运行脚本?

9
我使用Vagrant v1.5.1创建虚拟机群集。在所有VM完成配置后,是否有可能在其中一台上运行单个脚本? 我想要运行的脚本将设置从一个VM到所有其他VMs的无密码SSH。
例如,我的节点在Vagrant(CentOS 6.5)中实施如下:
- node1 - node2 - node3 - node4
我的 Vagrantfile 如下所示:
(1..4).each do |i|
 config.vm.define "node-#{i}" do |node|
  node.vm.box = "centos65"
  ...omitted..
 end
end

完成以上步骤后,我需要在node1上运行一个脚本,以便实现对node2、node3和node4的无密码SSH访问。

我知道可以在每个虚拟机被配置时运行脚本,但在这种情况下,我需要在所有虚拟机都启动并运行后才能运行此最后一个脚本。

Vagrant是否支持这种操作?

我意识到我也可以反向迭代。

r = 4..1
(r.first).downto(r.last).each do |i|
 config.vm.define "node-#{i}" do |node|
  node.vm.box = "centos65"
  ...omitted..
  if i == 1
   node.vm.provision "shell" do |s|
    s.path = "/path/to/script.sh"
   end
  end
 end
end

这将很好地工作,但实际上,我还需要在节点2到节点1、节点3和节点4之间设置无密码SSH。在上述方法中,这只能适用于节点1,而不适用于节点2(因为节点1不会被配置)。
如果有一个Vagrant插件可以允许在我的集群中所有节点之间使用密码SSH,那就更好了。
4个回答

5
这篇问题已经一年了,但我因为遇到了同样的问题而找到了它,所以这里是我用来解决问题的解决方法,希望对别人有用。
我们需要 "vagrant triggers" 来实现这个方法。关于 vagrant triggers 的事情在于它会触发你创建的每一个虚拟机,但我们想要确定所有虚拟机都已经启动。我们可以通过检查每一个虚拟机是否 UP 事件与最后一个被创建的机器相对应来做到这一点。
Vagrant.configure("2") do |config|

  (1..$machine_count).each do |i| 

  config.vm.define vm_name = "w%d" % i do |worker|

   worker.vm.hostname = vm_name
   workerIP = IP
   worker.vm.network :private_network, ip: workerIP

   worker.trigger.after :up do
     if(i == $machine_count) then
       info "last machine is up"
       run_remote  "bash /vagrant/YOUR_SCRIPT.sh"
     end   
   end

  end
 end
end

这适用于不支持Vagrant(VBox、VMWare)并行执行的提供商。


4
有没有办法在“最后一台机器启动”时触发一个脚本并在另一台机器上运行? 假设我创建了一个头节点和3个工作节点。一旦所有3个工作节点都启动,我想在我的头节点上执行某些操作。 - Djidiouf
这个问题问如何在所有节点启动后在节点1上运行命令。但是这并没有做到这一点。 - openCivilisation

2

Vagrant中没有“在所有虚拟机都完成配置后运行”的钩子,因此您需要自己实现。我能想到的几个选项:

1:在所有虚拟机都运行后运行SSH设置脚本。

例如,如果该脚本名为ssh_setup.sh并位于共享文件夹中:

$ for i in {1..4}; do vagrant ssh node$i -c 'sudo /vagrant/ssh_setup.sh'; done

2: 在部署期间为所有主机使用相同的SSH密钥

如果所有节点共享相同的无需密码的SSH密钥,您可以将所需的文件如authorized_keysid_rsa等复制到~.ssh中。


1
这对我很有帮助:我使用了每个虚拟机的自定义脚本,最后一个脚本通过ssh在第一个虚拟机上调用了后置脚本。
在Vagrantfile中:
require 'fileutils'

Vagrant.require_version ">= 1.6.0"

$max_nodes = 2
$vm_name = "vm_prefix"

#...<skipped some lines that are not relevant to the case >...
Vagrant.configure("2") do |config|
  config.ssh.forward_agent = true
  config.ssh.insert_key    = false
  #ubuntu 16.04
  config.vm.box = "ubuntu/xenial64"

  (1..$max_nodes).each do |i|
    config.vm.define vm_name = "%s-%02d" % [$vm_name, i] do |config|
      config.vm.hostname = vm_name
      config.vm.network "private_network", ip: "10.10.0.%02d" % [i+20], :name => 'vboxnet2'
      config.vm.network :forwarded_port, guest: 22, host: "1%02d22" % [i+20], id: "ssh"
      config.vm.synced_folder "./shared", "/host-shared"
      config.vm.provider :virtualbox do |vb|
        vb.name = vm_name
        vb.gui = false
        vb.memory = 4096
        vb.cpus = 2
        vb.customize ["modifyvm", :id, "--cpuexecutioncap", "100"]
        vb.linked_clone = true
      end

      # Important part:
      config.vm.provision "shell", path: "common_provision.sh"
      config.vm.provision "shell", path: "per_vm_provision#{i}.sh"

    end
  end
end

在磁盘上: (确保 post_provision.sh 至少拥有所有者执行权限:rwxr..r..)
vm$ ls /vagrant/
...<skipped some lines that are not relevant to the case >...
config.sh
common_provision.sh
per_vm_provision1.sh
per_vm_provision2.sh
per_vm_provision3.sh
...
per_vm_provisionN.sh
post_provision.sh
Vagrantfile
...<skipped some lines that are not relevant to the case >...

config.sh 文件中:
  num_vm="2" # should equal the $max_nodes in Vagrantfile
  name_vm="vm_prefix" # should equal the $vm_name in Vagrantfile
  username="user1"
  userpass="abc123"
    ...<skipped some lines that are not relevant to the case >...

common_provision.sh 文件中:
  source /vagrant/config.sh
  ...<skipped some lines that are not relevant to the case >...

  sed -r -i 's/\%sudo.*$/%sudo       ALL=(ALL:ALL) NOPASSWD:ALL/' /etc/sudoers
  sed -r -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
  service ssh reload

  # add user ${username}
  useradd --create-home --home-dir /home/${username}  --shell /bin/bash  ${username}
  usermod -aG admin ${username} 
  usermod -aG sudo ${username}
  /bin/bash -c "echo -e \"${userpass}\n${userpass}\" | passwd ${username}"

  # provision additional ssh keys
  # copy ssh keys from disk
   cp /vagrant/ssh/* /home/vagrant/.ssh
   cat /vagrant/ssh/id_rsa.pub >> /home/vagrant/.ssh/authorized_keys
   mkdir /home/${username}/.ssh
   cp /vagrant/ssh/* /home/${username}/.ssh
   cat /vagrant/ssh/id_rsa.pub >> /home/${username}/.ssh/authorized_keys

# not required, just for convenience
 cat >> /etc/hosts <<EOF
10.10.0.21    ${name_vm}-01
10.10.0.22    ${name_vm}-02
10.10.0.23    ${name_vm}-03
...
10.10.0.2N    ${name_vm}-0N
EOF
  ...<skipped some lines that are not relevant to the case >...

per_vm_provision2.sh文件中:
#!/bin/bash

  # import variables from config
  source /vagrant/config.sh

  ...<skipped some lines that are not relevant to the case >...

 # check if this is the last provisioned vm 
 if [ "x${num_vm}" = "x2" ] ; then
   ssh vagrant@10.10.0.21 -o StrictHostKeyChecking=no -- '/vagrant/post_provision.sh'
 fi

per_vm_provisionN.sh 文件中:
#!/bin/bash

  # import variables from config
  source /vagrant/config.sh

  ...<skipped some lines that are not relevant to the case >...

 # check if this is the last provisioned vm. N represents the highest number 
 if [ "x${num_vm}" = "xN" ] ; then
   ssh vagrant@10.10.0.21 -o StrictHostKeyChecking=no -- '/vagrant/post_provision.sh'
 fi

我希望我没有漏掉任何重要的东西,但总体来说,我认为这个想法是清晰的。
注意:Vagrant默认提供用于互相访问的ssh密钥。如果需要,您可以使用common_provision.sh添加自己的ssh密钥。

我猜想有更好的 Ruby 专家可以将所有内容编译成一个 Vagrantfile。 - VAS

1

添加一个已更新的答案。

vagrant-triggers插件于2018年5月被合并Vagrant 2.1.0中。

我们可以从trigger类中简单地使用only_on选项选项。

假设我们有以下配置:

servers=[
  {:hostname => "net1",:ip => "192.168.11.11"},
  {:hostname => "net2",:ip => "192.168.22.11"},
  {:hostname => "net3",:ip => "192.168.33.11"}
]

我们现在可以在最后一台机器启动后轻松执行触发器:
# Take the hostname of the last machine in the array
last_vm = servers[(servers.length) -1][:hostname]

Vagrant.configure(2) do |config|
    servers.each do |machine|
        config.vm.define machine[:hostname] do |node|

            # ----- Common configuration ----- #
            node.vm.box = "debian/jessie64"
            node.vm.hostname = machine[:hostname]
            node.vm.network "private_network", ip: machine[:ip]

            # ----- Adding trigger - only after last VM is UP ------ #
            node.trigger.after :up do |trigger|
                trigger.only_on = last_vm  # <---- Just use it here!
                trigger.info = "Running only after last machine is up!"
            end
        end
    end
end

我们可以检查输出并看到触发器只有在"net3"启动后才会真正触发:

==> net3: Setting hostname...
==> net3: Configuring and enabling network interfaces...
==> net3: Installing rsync to the VM...
==> net3: Rsyncing folder: /home/rotem/workspaces/playground/vagrant/learning-network-modes/testing/ => /vagrant
==> net3: Running action triggers after up ...
==> net3: Running trigger...
==> net3: Running only after last machine is up!

这样做只能在最后一台机器上运行,如果所有机器都启动后你想在另一台机器上运行该命令怎么办? - openCivilisation
  1. 是的,因为我只参考了"last_vm"。
  2. 您可以将其更改为任何其他虚拟机。
- RtmY
1
但是这样它只会在第一个虚拟机启动后运行,而不是在所有虚拟机都启动后运行。 - openCivilisation
请检查并查看 (: - RtmY

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接