码头工人

码头工人

我想创建 100 个虚拟服务器。它们将用于测试,因此它们应该易于创建和销毁。

  • 它们必须可以通过 SSH 从另一台物理机访问(我提供公共 ssh 密钥)
  • 它们必须有自己的 IP 地址,并且可以从另一个物理主机访问,例如ssh I.P.n.ossh 10.0.0.99IPv4 或 IPv6,私有地址空间可以,端口转发不行 - 所以这可能涉及设置网桥)
  • 他们必须安装基本的 UNIX 工具(最好是完整的发行版)
  • 它们必须有 /proc/cpuinfo、root 用户和网卡(这可能仅在计算机未完全虚拟化时相关)
  • 如果可以让它们运行可以远程连接的 X 服务器(使用 VNC 或类似的),则额外奖励

给定的最快方法(挂钟时间)是什么:

  • 主机系统运行Ubuntu 20.04并具有充足的RAM和CPU
  • LAN 有一个 DHCP 服务器(也可以使用预定义的 IP 范围)
  • 我不在乎使用哪种免费虚拟化技术(如果满足其他要求,容器化也可以)

我应该运行哪些实际命令/应该创建哪些文件?

我的感觉是,只要采用正确的技术,这项 50 条生产线的工作可以在几分钟内完成。

这几行可能可以分成几个 bash 函数:

install() {
  # Install needed software once
}
setup() {
  # Configure the virtual servers
}
start() {
  # Start the virtual servers
  # After this it is possible to do:
  #   ssh 10.0.0.99
  # from another physical server
}
stop() {
  # Stop the virtual servers
  # After there is no running processes on the host server
  # and after this it is no longer possible to do:
  #   ssh 10.0.0.99
  # from another physical server
  # The host server returns to the state before running `start`
}
destroy() {
  # Remove the setup
  # After this the host server returns to the state before running `setup`
}

背景

为了开发 GNU Parallel,我需要一种简单的方法来测试在 100 台机器上并行运行。

对于其他项目来说,能够创建一堆虚拟机,测试一些竞争条件,然后再次销毁机器也会很方便。

换句话说:这不适用于生产环境,安全性不是问题。

码头工人

基于@danielleontiev 的以下注释:

install() {
    # Install needed software once
    sudo apt -y install docker.io
    sudo groupadd docker
    sudo usermod -aG docker $USER
    # Logout and login if you were not in group 'docker' before
    docker run hello-world
}

setup() {
    # Configure the virtual servers
    mkdir -p my-ubuntu/ ssh/
    cp ~/.ssh/id_rsa.pub ssh/
    cat ssh/*.pub > my-ubuntu/authorized_keys
    cat >my-ubuntu/Dockerfile <<EOF
FROM ubuntu:bionic
RUN apt update && \
    apt install -y openssh-server
RUN mkdir /root/.ssh
COPY authorized_keys /root/.ssh/authorized_keys
# run blocking command which prevents container to exit immediately after start.
CMD service ssh start && tail -f /dev/null
EOF
    docker build my-ubuntu -t my-ubuntu
}

start() {
    # start container number x..y
    servers_min=$1
    servers_max=$2
    
    testssh() {
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/tmp/known root@"$1" echo "'$1'" '`uptime`'
    }
    export -f testssh
    setup_bridge() {
    # OMG why is this so hard
    # Default interface must have IP-addr removed
    # bridge must have IP-addr + routing copied from $dif, so it takes over default interface
    # Why on earth could we not just: brctl addif dock0 $dif - and be done?
    default_interface=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)')
    dif=$default_interface
    gw=$(ip -4 route ls | grep default | grep -Po '(?<=via )(\S+)')
    dif_ip=$(ip -4 route ls | grep default | grep -Po '(?<=src )(\S+)')
    echo Add bridge
    docker network create --driver bridge --subnet=172.20.0.0/16 --opt com.docker.network.bridge.name=dock0 net0
    # $dif must be up, but with no ip addr
    sudo ip addr flush dev $dif
    sudo brctl addif dock0 $dif
    sudo ifconfig dock0:ext $dif_ip
    sudo route add -net 0.0.0.0 gw $gw
    }
    # Start the containers
    startone() {
    id=$1
    net=$2
        docker run -d --rm --name ubuntu-$id-$net --network $net my-ubuntu
    docker inspect ubuntu-$id-$net
    }
    export -f startone

    setup_bridge
    echo Start containers
    seq $servers_min $servers_max | parallel startone {} net0 |
        # After this it is possible to do:
        #   ssh 10.0.0.99
        # from another physical server
        perl -nE '/"IPAddress": "(\S+)"/ and not $seen{$1}++ and say $1' |
    # Keep a list of the IP addresses in /tmp/ipaddr
    tee /tmp/ipaddr |
        parallel testssh
    docker ps
    route -n
}

stop() {
    # Stop the virtual servers
    # After there is no running processes on the host server
    # and after this it is no longer possible to do:
    #   ssh 10.0.0.99
    # from another physical server
    # The host server returns to the state before running `start`
    echo Stop containers
    docker ps -q | parallel docker stop {} |
    perl -pe '$|=1; s/^............\n$/./'
    echo
    echo If any containers are remaining it is an error
    docker ps
    # Take down bridge
    docker network ls|G bridge net|field 1| sudo parallel docker network rm
    # Re-establish default interface
    dif=$default_interface
    sudo ifconfig $dif $dif_ip
    # Routing takes a while to be updated
    sleep 2
    route -n
}

destroy() {
    # Remove the setup
    # After this the host server returns to the state before running `setup`
    rm -rf my-ubuntu/
    docker rmi my-ubuntu
}

full() {
    install
    setup
    start
    stop
    destroy
}

$ time full
real    2m21.611s
user    0m47.337s
sys     0m31.882s

这占用了 7 GB RAM全部的用于运行 100 个虚拟服务器。因此,您甚至不需要足够的 RAM 来执行此操作。

它扩展到 1024 个服务器,之后 docker 桥会抱怨(可能是由于每个桥接设备最多可以有 1024 个端口)。

该脚本可适应运行 6000 个容器(运行 > 1024 个 docker 容器),但在 6055 处它会阻塞(https://serverfault.com/questions/1091520/docker-blocks-when-running-multiple-containers)。

流浪汉

基于以下 @Martin 的注释:

install() {
    # Install needed software once
    sudo apt install -y vagrant virtualbox
}
setup() {
    # Configure the virtual servers
    mkdir -p ssh/
    cp ~/.ssh/id_rsa.pub ssh/
    cat ssh/*.pub > authorized_keys
    cat >Vagrantfile <<'EOF'
Vagrant.configure("2") do |config|
  config.vm.box = "debian/buster64"
  (1..100).each do |i|
    config.vm.define "vm%d" % i do |node|
      node.vm.hostname = "vm%d" % i
      node.vm.network "public_network", ip: "192.168.1.%d" % (100+i)
    end
  end

  config.vm.provision "shell" do |s|
    ssh_pub_key = File.readlines("authorized_keys").first.strip
    s.inline = <<-SHELL
      mkdir /root/.ssh
      echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys
      echo #{ssh_pub_key} >> /root/.ssh/authorized_keys
      apt-get update
      apt-get install -y parallel
    SHELL
  end
end
EOF
}
start() {
    testssh() {
        ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@"$1" echo "'$1'" '`uptime`'
    }
    export -f testssh
    # Start the virtual servers
    seq 100 | parallel --lb vagrant up vm{}
    # After this it is possible to do:
    #   ssh 192.168.1.111
    # from another physical server
    parallel testssh ::: 192.168.1.{101..200}
}
stop() {
    # Stop the virtual servers
    # After there is no running processes on the host server
    # and after this it is no longer possible to do:
    #   ssh 10.0.0.99
    # from another physical server
    # The host server returns to the state before running `start`
    seq 100 | parallel vagrant halt vm{}
}
destroy() {
    # Remove the setup
    # After this the host server returns to the state before running `setup`
    seq 100 | parallel vagrant destroy -f vm{}
    rm -r Vagrantfile .vagrant/
}

full() {
    install
    setup
    start
    stop
    destroy
}

start给出了很多警告:

NOTE: Gem::Specification.default_specifications_dir is deprecated; use Gem.default_specifications_dir instead. It will be removed on or after 2020-02-01.

stop给出这个警告:

NOTE: Gem::Specification.default_specifications_dir is deprecated; use Gem.default_specifications_dir instead. It will be removed on or after 2020-02-01.
Gem::Specification.default_specifications_dir called from /usr/share/rubygems-integration/all/gems/vagrant-2.2.6/lib/vagrant/bundler.rb:428.
NOTE: Gem::Specification.default_specifications_dir is deprecated; use Gem.default_specifications_dir instead. It will be removed on or after 2020-02-01.
Gem::Specification.default_specifications_dir called from /usr/share/rubygems-integration/all/gems/vagrant-2.2.6/lib/vagrant/bundler.rb:428.
/usr/share/rubygems-integration/all/gems/vagrant-2.2.6/plugins/kernel_v2/config/vm.rb:354: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/share/rubygems-integration/all/gems/vagrant-2.2.6/plugins/kernel_v2/config/vm_provisioner.rb:92: warning: The called method `add_config' is defined here
/usr/share/rubygems-integration/all/gems/vagrant-2.2.6/lib/vagrant/errors.rb:103: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
/usr/share/rubygems-integration/all/gems/i18n-1.8.2/lib/i18n.rb:195: warning: The called method `t' is defined here

每个虚拟机占用主机系统上 0.5 GB 的 RAM。

启动比上面的Docker机器慢很多。最大的区别在于 Vagrant 机器不必运行与主机相同的内核,而是完整的虚拟机。

答案1

我认为docker满足你的要求。

1)安装docker(https://docs.docker.com/engine/install/) 确保您已完成 Linux 安装后步骤 (https://docs.docker.com/engine/install/linux-postinstall/

2)我假设您有以下目录结构:

.
└── my-ubuntu
    ├── Dockerfile
    └── id_rsa.pub

1 directory, 2 files

id_rsa.pub是您的公钥,Dockerfile我们将在下面讨论

3)首先,我们要构建码头工人形象。这就像模板容器我们要跑的。每个容器都会是这样的物化我们的图像

4)为了构建图像,我们需要一个模板。它是Dockerfile

FROM ubuntu:bionic
RUN apt update && \
    apt install -y openssh-server
RUN mkdir /root/.ssh
COPY id_rsa.pub /root/.ssh/authorized_keys

CMD service ssh start && tail -f /dev/null

  • FROM ubuntu:bionic定义了我们的基础图像。您可以在 hub.docker.com 上找到 Arch、Debian、Apline、Ubuntu 等的基础
  • apt install部分安装ssh服务器
  • COPY from to将我们的公钥复制到容器中的位置
  • 在这里您可以添加更多RUN语句来执行其他操作:安装软件、创建文件等...
  • 最后一个很棘手。第一部分启动ssh服务器当我们启动容器时这是显而易见的,但第二个很重要 - 它运行阻塞命令,防止容器在启动后立即退出。

5) docker build my-ubuntu -t my-ubuntu- 构建图像。该命令的输出:

Sending build context to Docker daemon  3.584kB
Step 1/5 : FROM ubuntu:bionic
 ---> c3c304cb4f22
Step 2/5 : RUN apt update &&     apt install -y openssh-server
 ---> Using cache
 ---> 40c56d549c0e
Step 3/5 : RUN mkdir /root/.ssh
 ---> Using cache
 ---> c50d8b614b21
Step 4/5 : COPY id_rsa.pub /root/.ssh/authorized_keys
 ---> Using cache
 ---> 34d1cf4e9f69
Step 5/5 : CMD service ssh start && tail -f /dev/null
 ---> Using cache
 ---> a442db47bf6b
Successfully built a442db47bf6b
Successfully tagged my-ubuntu:latest

6)我们跑吧my-ubuntu。 (再次my-ubuntu是名字图像)。使用my-ubuntu-1从图像派生的名称启动容器my-ubuntu

docker run -d --rm --name my-ubuntu-1 my-ubuntu

选项:

  • -d 妖魔化用于在 bg 中运行容器
  • --rm容器停止后擦除容器。这很重要,因为当您处理大量容器时,它们会很快污染您的硬盘。
  • --name容器名称
  • my-ubuntu我们从图像开始

7) 图像正在运行。docker ps可以证明这一点:

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
ee6bc20fd820        my-ubuntu           "/bin/sh -c 'service…"   5 minutes ago       Up 5 minutes         my-ubuntu-1

8) 在容器中执行命令:

docker exec -it my-ubuntu-1 bash- 进入容器的bash.可以提供任何命令

9) 如果以上方式运行命令还不够,则执行docker inspect my-ubuntu-1和 grepIPAddress字段。对于我来说是172.17.0.2.

ssh [email protected]
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.6.15-arch1-1 x86_64)

10) 停止容器:docker stop my-ubuntu-1

11) 现在可以运行 100 个容器:

#!/bin/bash

for i in $(seq 1 100); do
    docker run -d --rm --name my-ubuntu-$i my-ubuntu
done

我的docker ps

... and so on ...
ee2ccce7f642        my-ubuntu           "/bin/sh -c 'service…"   46 seconds ago      Up 45 seconds                            my-ubuntu-20
9fb0bfb0d6ec        my-ubuntu           "/bin/sh -c 'service…"   47 seconds ago      Up 45 seconds                            my-ubuntu-19
ee636409a8f8        my-ubuntu           "/bin/sh -c 'service…"   47 seconds ago      Up 46 seconds                            my-ubuntu-18
9c146ca30c9b        my-ubuntu           "/bin/sh -c 'service…"   48 seconds ago      Up 46 seconds                            my-ubuntu-17
2dbda323d57c        my-ubuntu           "/bin/sh -c 'service…"   48 seconds ago      Up 47 seconds                            my-ubuntu-16
3c349f1ff11a        my-ubuntu           "/bin/sh -c 'service…"   49 seconds ago      Up 47 seconds                            my-ubuntu-15
19741651df12        my-ubuntu           "/bin/sh -c 'service…"   49 seconds ago      Up 48 seconds                            my-ubuntu-14
7a39aaf669ba        my-ubuntu           "/bin/sh -c 'service…"   50 seconds ago      Up 48 seconds                            my-ubuntu-13
8c8261b92137        my-ubuntu           "/bin/sh -c 'service…"   50 seconds ago      Up 49 seconds                            my-ubuntu-12
f8eec379ee9c        my-ubuntu           "/bin/sh -c 'service…"   51 seconds ago      Up 49 seconds                            my-ubuntu-11
128894393dcd        my-ubuntu           "/bin/sh -c 'service…"   51 seconds ago      Up 50 seconds                            my-ubuntu-10
81944fdde768        my-ubuntu           "/bin/sh -c 'service…"   52 seconds ago      Up 50 seconds                            my-ubuntu-9
cfa7c259426a        my-ubuntu           "/bin/sh -c 'service…"   52 seconds ago      Up 51 seconds                            my-ubuntu-8
bff538085a3a        my-ubuntu           "/bin/sh -c 'service…"   52 seconds ago      Up 51 seconds                            my-ubuntu-7
1a50a64eb82c        my-ubuntu           "/bin/sh -c 'service…"   53 seconds ago      Up 51 seconds                            my-ubuntu-6
88c2e538e578        my-ubuntu           "/bin/sh -c 'service…"   53 seconds ago      Up 52 seconds                            my-ubuntu-5
1d10f232e7b6        my-ubuntu           "/bin/sh -c 'service…"   54 seconds ago      Up 52 seconds                            my-ubuntu-4
e827296b00ac        my-ubuntu           "/bin/sh -c 'service…"   54 seconds ago      Up 53 seconds                            my-ubuntu-3
91fce445b706        my-ubuntu           "/bin/sh -c 'service…"   55 seconds ago      Up 53 seconds                            my-ubuntu-2
54c70789d1ff        my-ubuntu           "/bin/sh -c 'service…"   2 minutes ago       Up 2 minutes         my-ubuntu-1

我可以执行 fe docker inspect my-ubuntu-15,获取其 IP 并连接到 ssh 或使用 docker exec。

可以ping从容器到容器(安装iputils-ping以重现):

root@5cacaf03bf89:~# ping 172.17.0.2 
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=1.19 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.158 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.160 ms
^C
--- 172.17.0.2 ping statistics ---

注意从 bash 运行容器是快的解决方案。如果您想要可扩展的方法,请考虑使用kubernetesswarm

PS 有用的命令:

  • docker ps
  • docker stats
  • docker container ls
  • docker image ls

  • docker stop $(docker ps -aq)- 停止所有正在运行的容器

另外,请遵循 docs.docker.com 中的基础知识 - 花费 1 小时的时间可以获得更好的容器使用体验

额外的:

示例中的基础图像实际上是最小的图像。它没有 DE,甚至没有 xorg。您可以手动安装它(将软件包添加到RUN apt install ...部分)或使用已经包含您需要的软件的映像。快速谷歌搜索给了我这个(https://github.com/fcwu/docker-ubuntu-vnc-desktop)。我从未尝试过,但我认为它应该有效。如果您确实需要 VNC 访问,我应该尝试一下并在答案中添加信息

暴露到本地网络:

这可能很棘手。我确信可以通过一些晦涩的端口转发来完成,但简单的解决方案是更改运行脚本,如下所示:

#!/bin/bash

for i in $(seq 1 100); do
    docker run -d --rm -p $((10000 + i)):22 --name my-ubuntu-$i my-ubuntu
done

之后,您将能够使用主机 IP 访问容器:

ssh root@localhost -p 10001
The authenticity of host '[localhost]:10001 ([::1]:10001)' can't be established.
ECDSA key fingerprint is SHA256:erW9kguSvn1k84VzKHrHefdnK04YFg8eE6QEH33HmPY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[localhost]:10001' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.6.15-arch1-1 x86_64)

答案2

  • 创建虚拟网络

    要么使用 virtualbox

    或者通过使用泊坞窗,例如docker network create --driver=bridge --ip-range=10.0.190.0/24 --subnet=10.0.0.0/16 --aux-address='ip1=10.0.190.1' --aux-address='ip2=10.0.190.2' --aux-address='ip3=10.0.190.3' -o "com.docker.network.bridge.name=br0" br0:)

  • 如果你想要 virtualbox/kvm :

    准备一个pxe/http服务器和类似的发行版 SLAX或者阿尔卑斯Linux,使用slax和savechanges你的云构建一个系统,所有软件都预先打包,另一方面,这会产生很大的开销,但是使用像这样的工具集群SSH您可以通过运行同时触发您的命令

    cssh [email protected].{04..254} -p 22

  • dropbear使用 docker 时:通过 docker-compose 或手动将所有容器附加到指定网络,如果您想拥有 ssh 访问权限,您还可以修改 CMD 来运行

答案3

你可以使用流浪汉用于启动您的测试环境。一旦你编写了定义要运行的发行版、网络配置等,你就可以通过运行或只是启动来Vagrantfile启动机器vagrant up <vmname>vagrant up他们全部向上。 Vagrant 支持各种虚拟化提供商包括 Virtual Box、VMware、KVM、AWS、Docker...Vagrant 能够快速启动开发环境,因为它利用了预先构建的“box”文件而不是从头开始安装每个系统。同时 Vagrant 允许你运行你自定义的供应对于每个虚拟机使用安西布尔, 木偶,厨师,CF引擎或者只是一个简短的 shell 脚本。您可以在同一个 Vagrantfile 中混合和匹配不同的发行版。 SSH 访问是自动设置的。您可以通过运行来访问机器vagrant ssh <vmname>同步文件夹可以轻松地将文件从主机系统引入测试环境。


以下是详细步骤:

  1. 下载并安装 Vagrant 和您最喜欢的虚拟化提供商:

    $ sudo apt install -y vagrant virtualbox
    
  2. 创建 Vagrantfile 并包含以下内容:

    Vagrant.configure("2") do |config|
      config.vm.box = "debian/buster64"
      (1..100).each do |i|
        config.vm.define "vm%03d" % i do |node|
          node.vm.hostname = "vm%03d" % i
          node.vm.network "public_network", ip: "192.168.1.%d" % (99 + i)
        end
      end
    
      config.vm.provision "shell" do |s|
        ssh_pub_key = File.readlines("#{Dir.home}/.ssh/id_rsa.pub").first.strip
        s.inline = <<-SHELL
          mkdir /root/.ssh
          echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys
          echo #{ssh_pub_key} >> /root/.ssh/authorized_keys
          apt-get update
          apt-get install -y parallel
        SHELL
      end
    end
    
  3. 启动虚拟机:

    $ parallel vagrant up ::: vm{001..100}
    
  4. 通过 SSH 连接到虚拟机:Vagrant 方式(使用 Vagrant 生成的密钥):

    $ vagrant ssh vm001
    

    使用您自己的密钥(我们在配置阶段将其安装到虚拟机中):

    $ ssh vagrant@<IP>
    

    或者获得 root 访问权限:

    $ ssh root@<IP>
    
  5. 您可以通过运行来暂停虚拟机vagrant suspend,并在几天后将其启动以继续测试 ( vagrant up)。如果您有许多测试环境但磁盘空间有限,您可以销毁一些虚拟机并稍后重新创建它们。

  6. 销毁虚拟机并删除配置:

    vagrant destroy -f
    rm -rf Vagrantfile .vagrant
    

答案4

我建议自托管 Gitlab。它开箱即用地集成了 kubernetes 和 docker,并且可以实现您描述的几乎所有需要做的事情的自动化。

相关内容