根据 MA​​C 获取自动安装

根据 MA​​C 获取自动安装

短的:

我正在寻找一种 PXE 启动 Ubuntu 20.04 的方法,并使用新的自动安装进行完全无人值守的安装。但我希望user-data根据客户端的 MAC 在服务器端修改 YAML

我发现了什么

  • 我有启动内核选项,nocloud-net;s=http://...但我没有找到将自定义字符串作为 URL 的一部分发送的方法(或者根据本地 MAC 地址完全更改 URL)
  • 我看到early-commands其中说autoinstall在运行后将会刷新,但我没有找到任何有效的方法来使用它来将修改后的数据注入新的自动安装;例如,然后wget http://myurl/$MACgrep 该文件并修改已经运行的自动安装
  • 使用late-commands最后的选择,我确实可以wget http://myurl/$MAC && .. && ...这样做。例如设置静态 IP/GW/网络掩码,但它似乎更容易出错
  • 编辑:似乎我还有其他方法,但也需要在我预期的 Web 管理之外进行手动处理,即为每个客户端提供不同的 pxelinux.cfg 并在其中更改 URL,但这是通过 TFTP 提供的,因此没有服务器端脚本(除非有解决方法?)编辑#2:这可以工作,将 TFTP 和 HTTP 服务器指向相同的文件夹,并告诉 PHP 在 /pxelinux.cfg/AA-BB-CC-DD-EE-01 ... -02 ... -03 .. 等下为我的数据库中的每个 MAC 生成自定义文件,并在保存 DB 中的条目时检查/重新生成文件。至少保留了单一管理点的想法。但如果有人知道更好的解决方案,我会留下问题(请参阅上面的选项)

最终目标

我希望有一个“主”PXE 服务器,它是 HTTPS 服务器和 Web 管理的所在地,在那里我可以拥有一个包含所有设备(例如在 MySQL 中)以及与每个设备相关的所有设置的表格。然后,当我们部署新客户端(主要是一些愚蠢的信息亭之类的东西)时,我会选择它们的 MAC,启用 PXE 启动,给它们贴上标签,然后将它们发送到远程位置。该 MAC 和位置将通过 Web 管理输入到 MySQL 中,以及静态 IP、GW、DNS、浏览器主页、屏幕旋转等内容。一旦它们到达并且有人将它们连接起来,它们就会启动到无人值守的 PXE 安装,通过它,在此过程中它们会从 Web 服务器中提取配置(例如,user-data实际上将由 PHP 处理,并根据需要注入所需的配置),然后它就会 - 嗯... 正常工作。它也可以应用于live图像,只是方式不同。

我主要使用autoinstalland nocloud-netboot,一开始看起来很棒,我可以为每个客户端提供一个自定义的无人值守文件,除了一个事实,我无法识别一个客户端与另一个客户端。获取http://myserver/user-data?AA-BB-CC-DD-EE-FF似乎不在规范中,仅通过 IP 进行选择是行不通的,因为那些将是随机 DHCP,不幸的是,用 DHCP 预留来解决这个问题是一场噩梦,因为我们谈论的是 100 多个位置,每个位置都有自己的本地 DHCP,等等。

我没什么主意了,所以希望有人能参与进来。任何能让我动手的想法(除了late-commands)都很好!它不一定是 MAC,可以是 UIID,或者其他硬件 ID(序列号等),但它应该是唯一的,并且易于获取。MAC 通常是盒子外面的贴纸。

哦,如果你想知道我为什么这么反对late-commands……好吧……我并不反对,只是动态修改无人值守文件会更加灵活。我可以从一开始就设置主机名、IP、用户名、密码、磁盘大小等等。这比使用一些默认值启动要干净得多,然后尝试使用 bash 脚本遍历所有地方试图修复它(特别是磁盘/分区)。毕竟,这就是我们autoinstall首先拥有脚本的原因,而不是在第一次启动后重新做所有事情。

答案1

看来我终于解决了。这既简单又充满了错误和障碍。答案是肯定的 - 使用早期命令。但真相在于细节,所以答案很详细。

首先,准备好其余的环境,你可以查看我的另一篇文章,了解我执行 20.04 和 20.10 的 BIOS/UEFI PXE 启动的详细步骤: https://askubuntu.com/a/1292097/1080682

现在,当您的环境正常工作时(祝你好运),让我们根据通过 HTTP 中期安装提供的配置更改进行自定义自动安装。

因此,如果您按照我在链接上发布的指南操作,我会将我的 Ubuntu 用户数据保存在这里:

/var/www/html/ubuntu-server-20.04.1/user-data

使用类似这样的内容修改该文件(请注意,为了便于阅读,我已将其缩短):

#cloud-config

autoinstall:
  version: 1
  refresh-installer:
    update: yes
  apt:
    <apt stuff>
  identity:
    hostname: pxe-client
    password: $6$zN/uHJD1rEXD/ETf$q8CoBt3xXmBT37RslyWcpLT1za4RJR3QEtosggRKN5aZAAf6/mYbFEQO66AIPm965glBXB1DGd0Sf.oKi.Rfx/
    realname: pxe
    username: pxe
  keyboard: {layout: hr, toggle: toggle, variant: ""}
  early-commands:
    - curl -G -o /autoinstall.yaml http://10.10.2.1/user-data -d "mac=$(ip a | grep ether | cut -d ' ' -f6)"
  locale: en_US
  network:
    network:
      version: 2
      ethernets:
        eth0:
          dhcp4: yes
          dhcp6: no

现在,这个用户数据可以是真正的基础数据,我们真正需要的只是让它能够启用网络,并且.IPcurl中的一行代码是我的 HTTP 服务器的本地 IP(也是我的 PXE 服务器,因为我通过它提供其他配置文件和 ISO 映像等,但这并不重要)。early-commands10.10.2.1

使用您想要修改的任何内容并根据请求提供此文件。使用上面使用 curl 执行的操作,您实际上会向服务器请求类似以下内容的内容:

GET /user-data?mac=fa:fa:fa:00:0e:07

该部分fa:fa:fa:00:0e:07是服务器查询其自身接口后发送的内容。如果您有多个接口,您可能需要调整脚本,或者确保在早期安装步骤中只有一个接口处于启用状态。

我计划通过 PHP + MySQL 使用它,然后在 PHP 中使用$_GET["mac"]类似的方法获取它SELECT * FROM autoinstall-configs WHERE mac = '$_GET["mac"]';,然后从数据库表中的数据构建新的数据autoinstall.yaml并将其返回给 subiquity。

无论如何,你的回复不能有这句话autoinstall:!!

这里是 HTTP/PHP 将回复的最小示例,我只更改了主机名和用户名,并对其进行了修改,以便它能够通过 subiquity 语法检查,哦,并且排除了早期命令以免陷入循环:

  version: 1
  refresh-installer:
    update: yes
  apt:
    <apt stuff>
  identity:
    hostname: php-client
    password: $6$zN/uHJD1rEXD/ETf$q8CoBt3xXmBT37RslyWcpLT1za4RJR3QEtosggRKN5aZAAf6/mYbFEQO66AIPm965glBXB1DGd0Sf.oKi.Rfx/
    realname: php
    username: php
  keyboard: {layout: hr, toggle: toggle, variant: ""}
  locale: en_US
  network:
    network:
      version: 2
      ethernets:
        eth0:
          dhcp4: yes
          dhcp6: no
  ssh:
    allow-pw: true
    install-server: true
  late-commands:
    - poweroff

为了更清楚起见,以下是两个文件的差异:

diff /var/www/html/ubuntu-server-20.04.1/user-data /var/www/html/user-data

1,3d0
< #cloud-config
<
< autoinstall:
16c13
<     hostname: pxe-client
---
>     hostname: php-client
18,19c15,16
<     realname: pxe
<     username: pxe
---
>     realname: php
>     username: php
21,22d17
<   early-commands:
<     - curl -G -o /autoinstall.yaml http://10.10.2.1/user-data -d "mac=$(ip a | grep ether | cut -d ' ' -f6)"

所以几乎一样,只是不一样。需要将这些更改(删除autoinstall:early-commands:)传递到其余安装。您可以自行测试其他调整。

此后,安装将继续,无论通过答复请求提供什么新信息/user-data?mac=<installer-mac-address>

这为创建自己的虚拟机或服务器场等的 Web 管理打开了更多可能性。您不再需要为每个服务器或服务器组手动创建用户数据文件。您可以将每个服务器或服务器组发送给独特的配置,包括精确的静态 IP 地址、分区大小、不同的主机名、密码等。

Canonical,如果你从中挑选出一个想法,至少把我列入名单中:)

案子结了,欢呼!

答案2

根据您使用的 PXE,您可能能够在启动参数中使用变量。例如,使用 GRUB(通常用于 UEFI 机器)将提供${net_default_mac}MAC 地址的变量。

一般来说,我认为early-commands这是最好的选择。我不确定你尝试时失败的原因是什么。我认为你可以获取动态生成的user-data文件并覆盖该/autoinstall.yaml文件。

答案3

我实现了类似的东西,尽管我使用 Ansible/Ansible Tower 作为我的真相来源。本质上,我将 iPXE 脚本设置为使用请求自动安装配置的机器的 MAC 地址来访问存储在 OpenShift (kubernetes) 中的简单 flask 应用程序。然后,Ansible 运行并生成特定于机器的自动安装(使用 Jinja2 模板)配置文件并为其提供服务。没有早期脚本。

我对 Ubuntu 20.04 之前的版本的预置做了类似的事情。

import json
import requests
import subprocess
from flask import Flask, abort, request, redirect, send_from_directory

@autoinstaller.route('/ubuntu', defaults={'path': ''})
@autoinstaller.route('/ubuntu/<path:mac_address>')
@autoinstaller.route('/ubuntu/<path:mac_address>/meta-data')
@autoinstaller.route('/ubuntu/<path:mac_address>/user-data')
def get_autoinstall_config(mac_address):
    candidate_hostname = _isknownhost(mac_address)
    if candidate_hostname == None:
        abort(404)
    else:
        subprocess.run(["ansible-playbook", "playbooks/ubuntu/generate_autoinstall.yml", "--limit", f"{candidate_hostname}", "-e", f"candidate_mac={mac_address} candidate_hostname={candidate_hostname} authorized=True ansible_connection=local"])
        return send_from_directory(f'/tmp/{mac_address}', 'user-data')

_isknownhost是一个调用 ansible-inventory 来查找具有匹配 mac 地址的系统的函数。

def _isknownhost(mac_address):
    query = subprocess.Popen(["ansible-inventory", "--list"], stdout=subprocess.PIPE)
    hosts = json.loads(query.communicate()[0])['_meta']['hostvars']
    for system in hosts.keys():
        for hostvar in hosts[system].values():
            if hostvar == mac_address:
                return system
    return None

相关内容