使用Perl进行虚拟化环境的自动化管理和部署

运维 系统运维 自动化
本文介绍使用Perl创建脚本管理虚拟化环境的技巧,比如使用libvirt API管理KVM/QEMU/Xen虚拟机,使用vSphere SDK管理VMware环境等。掌握这些工具将有助于提高系统管理员的工作效率。

  概述


  虚拟化作为云计算的基础,是目前一个重要的趋势。通过虚拟化可以提高 IT 资源和应用程序的效率和可用性。基于内核的虚拟机 KVM 在 2008 年被 RedHat 收购后,在 IBM 和 RedHat 的联合推动下得到了全面的发展。最新发布的 RHEL 版本中已经全面支持了 KVM 虚拟机,并集成了一整套基于 libvirt 的管理工具 (virsh/virt-top/virt-install/virt-manager 等 )。虚拟化领域的主要厂商 VMware 的 vSphere 虚拟环境提供的 vSphere Client 让用户可通过直观的图形化方式管理 vCenter/ESX server/datacenter/cluster/VM 等对象。但是当被管理对象数量庞大时,使用 virt-manager 或者 vSphere Client 的图形化方式来处理一些日常事务就显得费时费力了。为此用户可以通过相应的 API 来编写程序管理这些日常事务。本文将介绍 libvirt API 和 vSphere SDK for Perl 在系统管理方面的应用。本文将对于开发人员、系统管理员、系统测试人员有所帮助。

使用 libvirt 管理 KVM 环境

  libvirt 是一套实现 Linux 虚拟化功能的开源 API,旨在提供一种单一的方式管理多种不同的虚拟化方案。目前 libvirt 支持如下的 hypervisor:

  •   KVM/QEMU
  •   Xen
  •   LXC
  •   OpenVZ
  •   VirtualBox
  •   VMware ESX, workstation, player
  •   Microsoft Hyper-V

  伴随着 libvirt,RHEL 最新的发行版还包含了一系列基于 libvirt 的工具用于简化虚拟机的维护管理:

  •   virt-install: 用于创建虚拟机
  •   virsh: 交互式/批处理 shell,可以用于完成虚拟环境的日常管理工作
  •   virt-manager: 一个图形化的界面 , 用于 Hypervisor 及其虚拟机的管理
  •   virt-clone: 用于虚拟机克隆
  •   virt-viewer: 安全连接虚拟机的图形控制台工具

  虽然 libvirt 本身由 C 开发 , 它提供了多种主流语言的绑定。系统管理员可以自由选择他们熟悉的语言,如 Python、Perl、Ruby、Java、PHP 等 .#p#

  使用 virsh 管理 KVM 虚拟机


  基于 libvirt 的管理工具中最常用的是 virsh。virsh 命令的格式如下:

  virsh [OPTION] COMMAND ARG

  在不提供任何参数时,virsh 提供一个交互式 shell。管理员可以通过 virsh 编写一些简单的 shell 脚本完成虚拟机/网络/存储的配置。下面的代码片段显示了 virsh 命令的使用方法。

  使用 ssh 协议连接到 KVM hypervisor:

  [root@BJGSSLA]# virsh connect qemu+ssh://9.9.9.9/system
  root@9.9.9.9's password:

  列举域 (guest VM):

  [root@BJGSSLA]# virsh list
  Id   Name   State
  --------------------------
  32   rhkvm   running
  33   rhkvm01   running
  34   xpkvm   running

  列举域的详细信息:

  [root@BJGSSLA]# virsh dominfo rhkvm
  Id:   32
  Name:   rhkvm
  UUID:   9d37e044-b134-c923-bbe6-0db40707ff9b
  OS Type:   hvm
  State:   running
  CPU(s):   1
  CPU time:   92.6s
  Max memory:   524288 kB
  Used memory:   524288 kB
  Persistent:   yes
  Autostart:   disable
  Managed save:   yes

  挂起,继续,重启域:

  [root@BJGSSLA]# virsh suspend rhkvm
  Domain rhkvm suspended
  [root@BJGSSLA]# virsh resume rhkvm
  Doamin rhkvm resumed
  [root@BJGSSLA]# virsh reboot rhkvm
  Domain rhkvm is being rebooted

  快照管理:

  [root@BJGSSLA]# virsh snapshot-create rhkvm
  Domain snapshot 1336311489 created
  [root@BJGSSLA]# virsh snapshot-list rhkvm
  Name   Create Time   State
  -----------------------------------------------------
  1336311489   2012-05-06 09:38:09 -0400   running
  [root@BJGSSLA]# virsh snapshot-revert rhkvm 1336311489

  注:在 KVM 虚拟环境中物理宿主机被称为节点 (node),每个 guest 被称为域 (domain) 。#p#

  基于 libvirt API 编写 Perl 脚本


  virsh 可以胜任大部分日常的工作,更复杂的需求可以通过 libvirt API 编程实现。本节将介绍基于如何使用 libvirt for Perl 来获得更加精细的虚拟机信息。

  CPAN 上的 Sys::Virt 模块即为 libvirt 的 Perl 绑定。首先根据 KVM 环境中 libvirt 的版本下载、编译、安装对应的 Sys-Virt 模块。本文的实验环境中使用 0.9.4 版 libvirt, 因此使用 Sys-Virt-0.9.4:

  http://search.cpan.org/~danberr/Sys-Virt-0.9.4/

  编译安装 Sys::Virt 模块:

  [root@BJGSSLA]# perl Makefile.PL
  [root@BJGSSLA]# make
  [root@BJGSSLA]# make install

  下面将通过编写一个简单的监控程序来介绍 libvirt API 的使用。在生产环境中,管理员经常需要监视虚拟机的 CPU 使用率。如果发现某些虚拟机的 CPU 使用率异常,管理员可以提醒虚拟机的主人或者挂起某些虚拟机来保证整个虚拟化环境的正常运转。

  基于 libvirt 开发的虚拟机性能监视工具 virt-top 的文档中提供了一个简化的实现算法:

  1. 对虚拟机的 cpuTime 进行周期性采样。此值可以通过 Sys::Virt::Domain 类的 get_info 方法得到,以纳秒为单位,记录了开机到目前所经过的 CPU 时间
  2. 假定两次采样的实际时间是 t1 和 t2,虚拟机 CPU 运行时间为 vt1 和 vt2
  3. 此虚拟机的 CPU 使用率可以按下式计算:
  %CPU = 100% * (vt2 - vt1) / ((t2-v1) * #_of_cores * 10^9)

  上述公式中的采样实际时间 (t1 和 t2) 可以通过 Perl 标准库模块 Time::HiRes 的 gettimeofday 方法获得。 #_of_cores 表示宿主机的 CPU 核数。此参数可以通过 Sys::Virt 类的 get_node_info 方法得到。

  清单 1. vcpu_util.pl
  #!/usr/bin/perl -w
  # usage: vcpu_util --uri uri --dom DOM_Name
  use strict;
  use warnings;
  use Time::HiRes;
  use Getopt::Long;
  use Sys::Virt;
  my ($uri, $dom);
  # 从命令行参数中获得 hypervisor 连接信息和虚拟机名
  GetOptions('uri=s' => \$uri, 'dom=s' => \$dom);
  # 获得 hypervisor 连接对象
  my $conn   = Sys::Virt->new(address => $uri) or die \
  "Cannot connect to the hypervisor: $uri. $!\n";
  # 获得虚拟机对象
  my $vm   = $conn->get_domain_by_name($dom) or die \
  "Cannot find the domain: $dom. $!\n";
  my ($start_time, $end_time, $start_vtime, $end_vtime);
  # 获得第一次采样的实际时间
  $start_time   = Time::HiRes::gettimeofday();
  # 获得第一次采样的虚拟机 CPU 时间
  $start_vtime   = $vm->get_info()->{'cpuTime'};
  # 等待下一次采样
  sleep(1);
  # 获得第二次采样的实际时间
  $end_time   = Time::HiRes::gettimeofday();
  # 获得第二次采样的虚拟机 CPU 时间
  $end_vtime   = $vm->get_info()->{'cpuTime'};
  # 获得宿主 CPU 的核数
  my $n_cores   = $conn->get_node_info()->{'cores'};
  # 计算虚拟机 CPU 的利用率
  my $util_rate   = 100 * ($end_vtime - $start_vtime) / (($end_time - \
  $start_time) * 1000000000 * $n_cores);
  printf "CPU utilization of $dom in $uri is: %.2f%%.\n", $util_rate;

  下面是某次运行的结果:

  [root@BJGSSLA]# ./vcpu_util.pl --uri qemu:///system --dom rhkvm
  CPU utilization of rhkvm in qemu:///system is: 4.46%

 #p#

  使用 vSphere SDK 管理 VMware 环境


  上一节介绍的 libvirt 是一套通用的 API,因此也可以用来管理 VMware 环境。但是在这方面 VMware 本身提供的 vSphere SDK 的功能则更具针对性。下面我们将学习如何使用 vSphere SDK for Perl 来管理 VMware 虚拟化环境。

搭建 vSphere SDK for Perl 开发环境

  可以从 VMware 的官方站点找到最新的 vSphere SDK for Perl。 读者需要注册 VMware 帐号,登录后根据需要选择合适的平台版本下载。

  在开始使用 vSphere SDK for Perl 之前,需要确保开发环境与 vSphere 之间的连接正常工作。我们可以通过访问 Managed Object Browser (MOB) 来测试连接状况:

  https://<vCenter_IP>/mob

  MOB 是一个基于 Web 的服务器端 (ESX/vCenter) 程序,用以浏览服务器端各种对象的属性方法等。如果浏览器能正确显示下面的页面,则说明开发环境与 vSphere 之间的连接正常。

  图 1. 使用 MOB 检查连接状态

  确保与 VMware 环境连接正常后,我们需要安装 vSphere SDK for Perl。安装前确保其所依赖的 OpenSSL/LibXML2/e2fsprogs 等工具已经安装。vSphere SDK for Perl 的安装过程非常简单,解压安装包,执行 vmware-install.pl,接受默认参数即可:

  [root@BJGSSLA]# tar – zxvf VMware-vSphere-CLI-4.X.X-XXXXX.i386.tar.gz
  [root@BJGSSLA]# /<extract_location>/sudo vmware-vsphere-cli-distrib/vmware-install.pl

  安装完成后可运行 vSphere SDK for Perl 的一些示例程序来测试开发环境是否可用,例如 datacenterlisting.pl。此程序可获得 ESX/ESXi 主机以及这些主机上虚机的列表:

  [root@BJGSSLA]# cd /usr/share/doc/vmware-vcli/samples/discovery/
  [root@BJGSSLA]# perl datacenterlisting.pl --server <vCenter_IP> \
  --datacenter <datacenter_name> \
  --username <user_name> --password <password>

  在本文的实验环境中,该脚本的运行结果为:

  [root@BJGSSLA]# perl datacenterlisting.pl --server 9.115.66.131 --datacenter MyDC \
  --username administrator --password passw0rd
  Hosts found:
  1: 9.115.208.49
  VM's found:
  1: WIN2K3
  2: WIN2K8R2
  3: RHEL5

 #p#

  与 VMware 环境交互的一般流程


  vSphere SDK for Perl 和 vSphere 环境之间的交互流程大致分以下 4 步:

  1. 验证命令行参数
  2. 连接远程服务器,传递参数
  3. 执行用户定义操作,如从远程服务器端查询对象、收集服务器端对象的信息、获取或者修改远程服务器端对象状态等
  4. 断开连接

  了解此交互流程将有助于我们理解示例程序的逻辑,更加清晰的组织自己开发的自动化脚本。下面通过解析一段简单的代码来说明这个交互过程。

  清单 2. simple_flow.pl

  #! /usr/bin/perl -w
  use strict;
  use warnings;
  # 导入 vSphere SDK for Perl 的运行支持模块
  # 此模块用来完成服务器端 - 客户端数据映射、载入客户端和服务器端之间的交互函数等
  use VMware::VIRuntime;
  # hash 结构 %opts 存放自定义命令行参数
  my %opts =(
  entity => {
  type => "=s",
  variable => "VI_ENTITY",
  help => "ManagedEntity type: HostSystem, etc",
  required => 1,
  },
  );
  # vSphere SDK for Perl 为所有脚本提供了一些基本的命令行参数,如 --server,--url 等
  # Opts::add_options 方法用以添加用户自定义参数
  Opts::add_options(%opts);
  # 解析命令行参数
  Opts::parse();
  # 验证命令行参数
  Opts::validate();
  # 连接远程服务器,vCenter 或 ESX server
  Util::connect();
  # 提取命令行参数 entity 的值
  my $entity_type = Opts::get_option('entity');
  # 根据 entity 值查询服务器端对象,
  # Vim::find_entity_views 返回服务器端对象对应的 Perl 视图
  my $entity_views = Vim::find_entity_views(view_type=>$entity_type);
  # 输出服务器端对象 Perl 视图的信息,如类型和名称等
  foreach my $entity_view (@$entity_views) {
  my $entity_name = $entity_view->name;
  Util::trace(0, "Found $entity_type: $entity_name\n");
  }
  # 断开与远程服务器的连接
  Util::disconnect();

  simple_flow.pl 展示了 vSphere SDK for Perl 脚本的一般结构,同时也演示了 Opts 包、Util 包和 Vim 包中一部分常见函数的用法。simple_flow.pl 在本文的实验环境中的运行结果为:

  [root@BJGSSLA]# ./simple_flow.pl --server 9.115.66.131 --username administrator \
  --password passw0rd --entity HostSystem
  Found HostSystem: 9.115.208.49
  [root@BJGSSLA]# ./simple_flow.pl --server 9.115.66.131 --username administrator \
  --password passw0rd --entity VirtualMachine
  Found VirtualMachine: WIN2K3
  Found VirtualMachine: WIN2K8R2
  Found VirtualMachine: RHEL55

 #p#

  VMware 服务器端对象与 Perl 视图


  用户编写 vSphere SDK for Perl 脚本的目的是访问和修改服务器端对象,如虚拟机、集群、快照等。为此我们需要了解 vSphere 服务器端对象的组织、服务器端对象与本地 Perl 对象的关系。

  vSphere 服务器端对象被称为 managed object。每个 managed object 都具有属性集 (properties) 并提供相关服务 (methods)。下图显示 managed object 的继承结构(部分 ):

  图 2. 服务器端 managed object 继承结构(部分 )

  抽象类 ManagedEntity 为服务器端对象定义了最基本的属性集(如 name,parent 等)和方法(如 Reload,Rename_Task 等)。常见的服务器端对象,如 ResoucePool、HostSystem、Datacenter、VirtualMachine 等都继承自 ManagedEntity,提供特定的属性及方法。感兴趣的读者可以通过访问 MOB 或者 vSphere SDK API 在线参考来深入了解各种 managed object 的细节。

  vSphere SDK for Perl 可以将服务器端对象的属性和方法映射为本地 Perl 对象,即服务器端对象的本地 Perl 视图,所以 Perl 视图可以看作服务器端对象的本地副本,其属性和方法和服务器端对象一一对应。不过需要注意的是,服务器端对象会实时更新,而 Perl 视图是静态的副本,所以当需要最新状态信息时,用户必须使用 Vim::update_view_data 显式刷新 Perl 视图。

  我们通常可以使用 Vim::find_entity_views 和 Vim::find_entity_view 函数来获得获得本地 Perl 视图(参考 simple_flow.pl 中的示例 )。有时 Vim::find_entity_views 返回结果过多,而 Vim::find_entity_view 返回的结果集中的第一个结果可能并不是我们需要的,此时我们可以使用 filter 参数细化查询条件、控制查询结果。下面的代码片段使用 guestFullName 属性进行过滤,获得所有 Windows 虚拟机的视图:

  清单 3. 使用 filter 参数过滤

  ...
  my $vm_views = Vim::find_entity_views(
  view_type => 'VirtualMachine',
  filter => {
  # config(VirtualMachineConfigInfo 类 ) 是 VirtualMachine 类的属性
  # guestFullName(xsd:string 类 ) 是 VirtualMachineConfigInfo 类的属性
  'config.guestFullName' => qr/Windows/
  }
  );
  foreach my $vm (@$vm_views) {
  print "Name: " . $vm->name . "\n";
  }
  ...

  大部分服务器端 managed object 拥有大量的属性,而我们编写任务脚本时往往只对其中的一部分感兴趣。为得到某个属性值而构造完整的 Perl 视图是非常低效的,为此我们可以通过 properties 参数来指定 Perl 视图中需要的属性:

  清单 4. 使用 properties 参数获取必要属性

  ...
  my $vm_view = Vim::find_entity_view(
  view_type => 'VirtualMachine',
  filter => { 'name' => 'foo' },
  # 只获取 runtime 属性中的 powerState 属性
  properties => [ 'runtime.powerState' ]
  );
  # 读取 Perl 视图的 powerState 属性
  my $state = $vm_view->runtime->powerState;
  ...

 #p#

  使用 vSphere SDK for Perl 完成虚拟机的部署和定制


  虚拟机模板是一种可重用的虚拟机映像,通过模板来部署虚拟机可以避免在安装系统过程中很多重复操作,极大地提高了效率。但是即使使用模板来部署系统,我们仍然需要手动为各个虚拟机完成一些定制性的工作,如配置 IP、主机名之类。这些定制化工作可以在部署模板时使用 vSphere 提供的 Customization Wizard 或者 Customization Specification 文件来完成。Customization Wizard 以向导的形式让用户输入自定义信息,用户也可以把定制信息保存为 Customization Specification 文件以便以后复用。对于小规模的部署,这些工具已经足够。但是当规模变大时,手动输入、编辑、维护这些信息也会消耗相当多的精力。下面给出的示例代码将自动化地完成虚拟机部署以及网络配置工作。

  w2k8_deploy.pl 演示了如何通过 Window 2008R2 模板自动部署虚拟机并配置其网络。该脚本适用于 XP/2003 以后的 Windows 版本,包括 Windows 2008/Windows 2008 R2/Vista/Win 7 sp1。其中 Windows 2008R2 和 Win7 sp1 的定制需要 vCenter 4.1 update 1。具体 Windows Guest OS 定制化支持请参考 vSphere 相关文档。

  清单 5. w2k8_deploy.pl

  #!/usr/bin/perl -w
  use strict;
  use warnings;
  use VMware::VIRuntime;
  Opts::parse();
  Opts::validate();
  Util::connect();
  # 部署 Windows 目标虚机并配置网络
  deploy_W2K8();
  Util::disconnect();Virt-top documentation
  sub deploy_W2K8 {
  my $vmhost = "9.115.208.49";   # 目标虚机所在 ESX server 的地址
  my $ds = "datastore1";   # 目标虚机所在的 datastore
  my $vm_template = "WIN2K8R2";   # 部署目标虚机用的模板
  my $respool = "ResPool";   # 目标虚机所在的 resource pool
  my $vm_name = "WIN2K8R2A";   # 目标虚机的虚机名
  # 获得模板视图
  my $vm_template_view = Vim::find_entity_view(
  view_type => 'VirtualMachine',
  filter => {name => $vm_template}
  );
  # 获得 ESX server 视图
  my $vmhost_view = Vim::find_entity_view(
  view_type => 'HostSystem',
  filter => {name => $vmhost}
  );
  # 获得 resource pool 视图
  my $respool_view = Vim::find_entity_view(
  view_type => 'ResourcePool',
  filter => {name => $respool}
  );
  # 获得 datastore 视图
  my $ds_view = Vim::find_entity_view(
  view_type => 'Datastore',
  filter => {name => $ds}
  );
  # 目标虚机重定位信息,指定目标虚机的 datastore/host/resource pool
  my $relocate_spec = VirtualMachineRelocateSpec->new(
  datastore => $ds_view,
  host => $vmhost_view,
  pool => $respool_view,
  );
  # 自定义 Guest Windows os 的网络配置信息
  # 主机名 /DNS 域 /IP 地址 / 网关 / 子网掩码 /DNS 服务器 / 用户及组织 / 序列号 / 密码等
  my $host_name = "WIN2K8R2A";
  my $domain = "cn.ibm.com";
  my $ip_address = "9.115.208.62";
  my @gateway = ("9.115.208.1");
  my $netmask = "255.255.255.0";
  my @dnsServers = ("9.181.2.101", "9.181.2.102");
  my $full_name = "IBMCN";
  my $org_name = "IBMCN";
  my $prod_ID = "";
  my $password = "passw0rd";
  # Windows Guest OS 的定制不需要指定 Global IP 设置
  my $cust_global_settings = CustomizationGlobalIPSettings->new();
  # 加入 workgroup 组。
  # 若使用域配置,需提供 domainAdmin/domainAdminPassword/joinDomain 参数
  my $cust_identification = CustomizationIdentification->new(
  joinWorkgroup => "workgroup",
  );
  my $cust_gui_unattended = CustomizationGuiUnattended->new(
  autoLogon => 1,
  autoLogonCount => 1,
  timeZone => 190,
  password => CustomizationPassword->new(
  plainText => "true",
  value => $password
  ),
  );
  my $cust_user_data = CustomizationUserData->new(
  computerName => CustomizationFixedName->new(name => $host_name),
  fullName => $full_name,
  orgName => $org_name,
  productId => $prod_ID,
  );
  my $win_prep = CustomizationSysprep->new(
  guiUnattended => $cust_gui_unattended,
  identification => $cust_identification,
  userData => $cust_user_data,
  );
  my $cust_IP_settings = CustomizationIPSettings->new(
  dnsDomain => $domain,
  dnsServerList => \@dnsServers,
  ip => CustomizationFixedIp->new(ipAddress => $ip_address),
  gateway => \@gateway,
  subnetMask => $netmask,
  );
  my $cust_adapter_mapping = CustomizationAdapterMapping->new(
  adapter => $cust_IP_settings,
  );
  my $cust_adapter_mapping_list = [$cust_adapter_mapping];
  my $cust_spec = CustomizationSpec->new(
  globalIPSettings => $cust_global_settings,
  identity => $win_prep,
  nicSettingMap => $cust_adapter_mapping_list,
  );
  my $clone_spec = VirtualMachineCloneSpec->new(
  powerOn => 1,
  template => 0,
  location => $relocate_spec,
  customization => $cust_spec,
  );
  # 启动任务
  $vm_template_view->CloneVM_Task(
  folder => $vm_template_view->parent,
  name => $vm_name,
  spec=>$clone_spec
  );
  }

  CloneVM_Task 方法执行定制化的模板部署,其中 folder 参数确定目标虚机所在目录,name 参数确定目标虚机的虚机名,VirtualMachineCloneSpec 类型的 spec 参数用于自定义虚拟机克隆的过程(包括虚拟机硬件配置的定制,Guest OS 定制等 )。可以看到前面大段的代码都是在构造 VirtualMachineCloneSpec 对象。下面的类图显示了各定制对象之间的关系:

  图 3. VirtualMachineCloneSpec 组成

  windows 2003/xp 以及之前的版本的定制化流程稍有不同。主要表现在:

  1. 构造 CustomizationSysprep 时需要提供 CustomizationLicenseFilePrintData 对象作为参数。至于 CustomizationLicenseFilePrintData 对象的具体构造方法,读者可以参考 vSphere SDK API 在线文档。
  2. 在 vCenter 端需要准备 sysprep 文件 (vista/2008 之后的 OS 具有内置的 sysprep,不再需要在 vCenter 端准备 )。从 OS 安装介质中找到 DEPLOY.cab,解压到 vCenter sysprep 的相关 OS 目录下:
  • 若 vCenter 安装在 2003 Server 上,此路径为:

  C:\Documents and Settings\All Users\Application Data\VMware\VMware VirtualCenter\sysprep\<os-level>\

  • 若 vCenter 安装在 2008 Server 上,此路径为:

  C:\ProgramData\VMware\VMware VirtualCenter\sysprep\<os-level>\

  在实际应用中,考虑的代码的可重用性,比较合适的方法是将需要定制化的参数放到外部文件中(例如使用 XML 格式进行组织 ),以后每次批量部署虚拟机的时候只需要改动必要部分即可。但是为了突出重点,尽可能用简单的例子来说明自动化配置的逻辑流程,这里仍然把参数直接写在配置流程中。

  Linux 的定制化过程和 Windows 大致相同,本文不再赘述。其主要区别在于构造 CustomizationSpec 时传入 CustomizationLinuxPrep 对象作为参数。CustomizationLinuxPrep 和 CustomizationSysprep 均继承自 CustomizationIdentitySettings。

责任编辑:黄丹 来源: developerWorks
相关推荐

2010-12-24 14:46:31

Perl脚本

2009-09-07 09:15:42

自动化服务虚拟化环境

2009-05-20 19:10:13

虚拟化绿色化自动化

2024-09-13 15:32:18

2022-11-15 17:07:40

开发自动化前端

2024-01-24 18:50:21

WebFTP服务器

2009-04-16 17:14:52

2023-11-21 22:48:50

2009-03-04 17:43:31

虚拟化自动化惠普

2010-12-06 09:59:58

2011-12-02 08:15:38

IP网络

2021-02-05 10:33:47

云计算自动化云计算虚拟化

2011-12-01 09:46:57

2021-04-19 14:00:03

ExchangelibPython邮箱自动化管理

2010-11-30 16:26:38

2014-04-22 09:26:49

Ubuntu 14.0虚拟化

2013-11-28 10:37:36

虚拟桌面环境

2013-04-07 17:38:08

2013-05-02 13:02:59

开发环境自动化团队

2021-10-14 06:52:47

自动化开发环境
点赞
收藏

51CTO技术栈公众号