SDB:KIWI Cookbook Data Separation
所有 KIWI 编辑
数据分离或处理分区
本示例提供了一种将应用程序数据与操作系统数据分离的简单方法。
无论在何种情况下讨论 OEM 镜像,总有人会问到分区。然而,大多数人真正想要的是通过分区将应用程序数据与操作系统数据分离。这其中存在微妙但重要的区别,将在下面解决。本教程展示了如何为 OEM 镜像实现数据分离。
通过分区进行数据分离
准备时间
- 20 分钟
烹饪时间
- 15 分钟
配料
- 一个正在运行的 openSUSE 11.1、11.2 或 11.3 系统
- 一个 openSUSE 11.1、11.2 或 11.3 仓库
- 已安装最新版本的 KIWI 工具集
- 大约 1 GB 的可用磁盘空间
分区与通过分区进行数据分离
在谈论分区时,必须考虑所有可能性。这包括任意复杂的分区方案,可能包含或不包含操作系统关心的目录的分区。这种分区方式在常规 SUSE 交互式安装和通过 AutoYaST 进行安装期间都受支持。在这种情况下,如果需要,用户可以为 /opt、/var、/usr、/home 等创建分区。基本上是操作系统具有“特殊”含义的目录。
这适用于交互式安装和 AutoYaST,因为在目标系统上实际安装软件之前会运行一个过程。一旦软件安装开始,软件包就会简单地解压到适当的目标目录,并且安装程序本身并不关心目标目录是否挂载到特定分区,或者给定的目录是否只是根 (/) 分区的一部分。
对于 Kiwi,这种方案行不通。创建 Kiwi 镜像时,软件安装发生在由 --root 参数提供的目录中,用于准备步骤。在这种安装模式下,从定义上来说,没有分区的概念,唯一存在的是一个目录。在创建步骤中,该目录“简单地”转换为磁盘镜像,用于 vmx 或 oem 镜像类型。在 OEM 安装期间,磁盘镜像随后转储到目标存储介质,从而最终得到基本上是一个分区(不包括交换空间)的系统。这种实现的原因可以解释如下
- 在从 ISO 镜像或 USB 驱动器将 OEM 系统安装到目标机器时,没有在安装之前运行的过程。与交互式和 Auto YaST 安装不同。一旦安装介质启动,我们就进入了 Kiwi 创建的过程,该过程将镜像转储到目标存储设备。因此,任何分区方案都必须集成到这个自安装过程中。
- 分区可以任意复杂,具体取决于目标系统存储配置、主分区和扩展分区的用法、文件系统类型、分区大小等。这很快会导致管理上的噩梦。包含探测、计算、错误处理等的通用分区代码不仅难以实现和维护在极其最小的自安装环境中,而且还存在相当大的风险,即在 OEM 转储后最终会得到一个无法工作的系统。如果这种情况发生在客户那里,那将是一个相当尴尬的事件。
- 在应用程序的上下文中,通常不需要通用分区。当人们在应用程序的上下文中谈论分区时,通常需要的是应用程序数据与被认为是操作系统一部分的数据(例如 /usr、/bin、/sbin 等)的分离。这就是通用分区与通过分区进行数据分离之间的微妙但重要的区别。
考虑到这一点,本示例的其余部分将提供实现通过分区进行数据分离所需的所有信息,而无需在 Kiwi 中需要通用分区功能的支持。
Kiwi 镜像配置
本示例使用一个非常简单的 config.xml 文件,因为主要重点是创建数据分区,该分区挂载到名为 myData 的目录。
基本设置
创建一个目录,用作 Kiwi 配置目录。
config.xml 文件
使用您喜欢的编辑器创建 /tmp/dataSep_exa/config.xml。您可以剪切和粘贴下面的示例文件,从头开始创建自己的内容,或修改示例以包含您想要包含的软件包。
<?xml version="1.0" encoding="utf-8"?>
<image schemaversion="4.7" name="suse-11.3-oem-dataseparation">
<description type="system">
<author>Robert Schweikert</author>
<contact>rschweikert at novell dot com</contact>
<specification>
openSUSE 11.3 based example showing data separation based on
partitions
</specification>
</description>
<preferences>
<type image="oem" filesystem="ext4" boot="oemboot/suse-11.3" installiso="true">
<oemconfig>
<oem-boot-title>Data-Separation</oem-boot-title>
<oem-home>false</oem-home>
<oem-swap>true</oem-swap>
<oem-swapsize>4096</oem-swapsize>
<oem-systemsize>8192</oem-systemsize>
</oemconfig>
</type>
<version>1.0.0</version>
<packagemanager>zypper</packagemanager>
<keytable>us.map.gz</keytable>
<timezone>US/Eastern</timezone>
<rpm-excludedocs>true</rpm-excludedocs>
</preferences>
<users group="root">
<user pwd="linux" pwdformat="plain" home="/root" name="root"/>
</users>
<users group="users">
<user pwd="linux" pwdformat="plain" home="/home/tux" name="tux"/>
</users>
<repository type="yast2">
<source path="opensuse://11.3/repo/oss/"/>
</repository>
<packages type="image" patternType="plusRecommended">
<package name="kernel-default"/>
<package name="ifplugd"/>
<package name="python"/>
<package name="vim"/>
<opensusePattern name="default"/>
</packages>
<packages type="bootstrap">
<package name="filesystem"/>
<package name="glibc-locale"/>
</packages>
</image>
之前的配置文件中没有什么特别之处。请注意,<oem-systemsize> 元素用于限制操作系统镜像的大小;在本例中为 8 GB。在 8 GB 内,几乎可以安装标准 SUSE 分发版中分发的所有软件包。示例 config.xml 文件中选择的软件包和模式反映了一个最小集合,因为该示例旨在演示概念而不是生成一个有用的设备。包含 Python 以执行用于创建数据分离的脚本。
<oem-systemsize> 元素限制了根分区使用的存储空间,从而使存储设备上的任何额外空间未被触及。如果没有 <oem-systemsize> 元素,根系统镜像将扩展到设备上的可用存储空间,从而没有空间用于额外的分区操作。因此,如果您需要在系统转储到目标存储设备后创建额外的分区,则需要使用 <oem-systemsize> 元素。
如果您在构建中包含自己的应用程序,您可能需要添加适当的磁盘空间来容纳应用程序的二进制文件。不要添加应用程序数据空间,因为我们将把数据放在一个单独的分区上,毕竟这就是本示例的重点。
config.sh 文件
配置的 config.sh 脚本未显示,因为它几乎是一个“标准”config.sh,它删除信息文件等,如之前的教程中所示。
分区设置
首先在 Kiwi 配置树中创建一个用于脚本的位置。
然后使用您喜欢的编辑器创建文件 /tmp/dataSep_exa/root/sbin/setUpPartitions。剪切和粘贴下面的示例脚本以跟随示例或根据您的需要修改脚本。
#!/usr/bin/python
import os
import time
def getSizeAndUnit(valueStr):
"""Extract number and unit information from a string of the format 111MB"""
size = ''
idx = 0
for char in valueStr:
if char.isdigit() or char == '.':
size += char
idx += 1
unit = valueStr[idx:]
return size, unit
diskInfoCmd = '/usr/sbin/parted %s print' %(os.environ["imageDiskDevice"])
diskInfo = os.popen(diskInfoCmd).readlines()
partCnt = 0
diskSize = ''
diskUnit = ''
lastPartEnd = ''
lastPartEndUnit = ''
foundPartTbl = None
numPartitions = 0
for diskLn in diskInfo:
if diskLn.find('Disk') != -1:
diskSize, diskUnit = getSizeAndUnit(diskLn.split(':')[-1].strip())
continue
if diskLn.find('Number') != -1:
foundPartTbl = 1
continue
if foundPartTbl and diskLn.split():
numPartitions += 1
lastPartEnd, lastPartEndUnit = getSizeAndUnit(diskLn.split()[2])
if (lastPartEnd == diskSize) and (lastPartEndUnit == diskUnit):
print "No free space on device"
exit(1)
# Create the partition using all available space
partCmd = '/usr/sbin/parted %s mkpart primary %s%s %s%s >& /dev/null' %(
os.environ["imageDiskDevice"],
lastPartEnd,
lastPartEndUnit,
diskSize,
diskUnit)
os.system(partCmd)
# Force the partition table to be re-read
partReadCmd = 'partx -a %s >& /dev/null' %(os.environ["imageDiskDevice"])
os.system(partReadCmd)
# Create the file system on the new partition
fsCmd = '/sbin/mkfs -t ext4 %s%d >& /dev/null' %(os.environ["imageDiskDevice"],
numPartitions + 1)
os.system(fsCmd)
# Detremine disk id based on existing fstab entry
fstabData = open('/etc/fstab', 'r').readlines()
diskID = ''
for fstabLn in fstabData:
if fstabLn.find('by-id') != -1:
diskID = fstabLn.split()[0]
break
# Create an entry in /etc/fstab
fstab = open('/etc/fstab', 'a')
fstab.write('\n\n#Autogenerated data partition information\n')
fstab.write(diskID[:diskID.index('part')])
fstab.write('part%d' %(numPartitions + 1))
fstab.write(' /myData')
fstab.write(' ext4')
fstab.write(' defaults 1 2\n')
fstab.close()
# Give the system some time to catch up to the changes
time.sleep(10)
# Create a mount point and mount he new partition
os.mkdir('/myData')
mntCmd = '/bin/mount /myData >& /dev/null'
os.system(mntCmd)
# Clean up
os.system('rm /tmp/partSetup')
bootInfo = open('/etc/init.d/boot.local', 'r').readlines()
bootLocal = open('/etc/init.d/boot.local', 'w')
skipBlock = None
for ln in bootInfo:
if ln.find('# Begin init data setup') != -1:
skipBlock = 1
if ln.find('# End init data setup') != -1:
skipBlock = None
continue
if skipBlock:
continue
bootLocal.write(ln)
bootLocal.close()
os.system('rm sbin/setUpPartitions')
保存文件并添加执行权限。
让我们更详细地了解一下脚本。首先定义了一个将“12.6GB”形式的字符串分解为两个字符串的函数。返回的第一个字符串是大小,“12.6”在本例中,第二个字符串是单位。“GB”在本例中。
在函数定义之后,我们收集了 parted /dev/sda print 提供的信息,以便解析数据并提取对我们有用的信息。
在本教程中,假设镜像安装在第一个发现的存储设备上(/dev/sda),并且数据分区也将位于该设备上。
for 循环只是检查 parted 命令的输出并提取感兴趣的数据。存储设备的尺寸存储在从以 Disk 开头的 parted 输出行中提取的 diskSize 变量中。存储尺寸的报告单位存储在 diskUnit 变量中。该循环还计算了预先存在的分区数(numPartitions 计数器)并将最后一个现有分区结束的位置存储在 lastPartEnd 变量中。最后一个分区的单位存储在 lastPartEndUnit 变量中。
在本教程中,有两个预先存在的分区,根分区 (/) 和交换分区。
快速比较最后一个分区的结束位置和磁盘大小,确定是否有可用空间。
在确认有可用空间后,脚本只需创建一个从先前最后一个分区的末尾开始并占用设备上所有可用空间的单个分区。可以在此处插入任意算法以创建所需的分区布局。但是,请记住,任何给定的存储设备最多只能有 4 个主分区。
分区创建后,在分区上创建文件系统,在本例中为 ext4。
接下来,脚本强制运行中的内核重新读取分区表,partx 命令,以确保在 /dev 中创建适当的设备节点。
在创建新的设备节点后,脚本将新行附加到 /etc/fstab 文件,以确保我们的分区在启动时挂载。在短暂的睡眠后,让系统有时间赶上所有更改,然后创建挂载点目录并挂载新分区。
最后但并非最不重要的是,脚本清理 boot.local 文件(如下所示并讨论),然后从系统中删除自身。不留下任何分区魔术刚刚发生的痕迹。
启动分区过程
从设备的用户的角度来看,我们希望分区设置是不可见的。此外,由于我们可以完成所有需要做的事情,只需在运行的系统上执行,因此我们只需在启动过程中启动分区脚本,而不必担心使用 Yast Firstboot 机制。
在 Kiwi 配置树中创建 init.d 目录。
使用您喜欢的编辑器创建 /tmp/dataSep_exa/root/etc/init.d/boot.local 并按照示例剪切和粘贴下面的脚本。
#! /bin/sh
# Begin init data setup
if [ -f /tmp/partSetup ]; then
/sbin/setUpPartitions
fi
# End init data setup
您可以看到分区脚本中使用的注释以及 Kiwi 配置树中触发文件的测试。
为了更模块化的设计,分区脚本不应清理 boot.local,该任务应由单独的脚本处理。但是,对于本示例,职责的混淆是可以接受的。在生产环境中,应该明确区分这些问题。无论如何,boot.local 脚本都很简单。不要忘记为 boot.local 设置执行位并在 Kiwi 配置树中创建触发文件。
这完成了配置树的设置。
构建并测试示例
与之前的教程相比,构建没有什么特别之处。
现在创建一个具有任意大小的磁盘镜像(在本例中为 250 GB)。
使用磁盘镜像,现在我们可以使用 qemu 测试 Kiwi 创建的 OEM 镜像。
镜像启动后,您将看到像往常一样自安装过程,然后是安装系统的启动过程。到达登录屏幕后,您可以以 root 用户身份登录,密码为 linux,并使用以下命令列出分区表
您将看到系统现在有 3 个分区。如果您运行 mount 命令,您将看到 /dev/sda3 挂载在 /myData 上