![在用户之间迁移非特权 LXC 容器](https://linux22.com/image/56012/%E5%9C%A8%E7%94%A8%E6%88%B7%E4%B9%8B%E9%97%B4%E8%BF%81%E7%A7%BB%E9%9D%9E%E7%89%B9%E6%9D%83%20LXC%20%E5%AE%B9%E5%99%A8.png)
我有一个 Ubuntu 14.04 服务器安装,它充当 LXC 主机。它有两个用户:user1 和 user2。
user1 拥有一个非特权 LXC 容器,该容器使用目录(/home/user1/.local/... 内部)作为后备存储。
如何为 user2 制作容器的完整副本?我不能只复制这些文件,因为它们映射的所有者范围从 100000 到 100000+,这些所有者绑定到 user1。
另外,我认为这基本上是同一个问题,如何安全地备份我的 user1 的 LXC 容器以便稍后在另一台机器和/或用户上恢复它?
答案1
我现在知道该怎么做了。如果您无法理解此解释,请回复,但也要确保您已阅读我在底部给出的读物中有关用户的内容
初步假设
我将坚持以下假设,这些假设是从你的问题中得到的延伸:
- 主机有 a
user1
和 auser2
,如果信息不特定于一个,我们将使用userX
- 容器将由我们将渲染为的变量命名
$container
user1
和的主文件夹将以 Bash 中称为和 的user2
表示法给出。~user1
~user2
- 为了简洁起见,我们假设从属 UID 和 GID 范围为 100000..165536
user1
和 200000..265536user2
- 的根 FS 文件夹
$container
将呈现为$rootfs
,无论它最终位于何处 (~userX/.local/share/lxc/$container/rootfs
) - 容器配置默认位于
~userX/.local/share/lxc/$container/config
移动容器
有两个相关的数据来管理userns
容器:
- 组成文件夹的文件/文件夹的所有者和组
$container
- 在两个位置分配的从属 UID 和 GID:分别
/etc/sub{uid,gid}
用于用户帐户(通过 操作usermod --{add,del}-sub-{uid,gid}s
)和配置lxc.id_map
中$container
(~userX/.local/share/lxc/$container/config
)- 我不确定是否可以在容器配置中为每个容器定义不同的范围。例如,如果主机用户
userX
有 65536 个从属 GID 和 UID,则可能可以分配 5000 到 65 个不同的容器,但我尚未测试该假设。 - 但可以肯定的是,此设置会与 LXC 进行通信,这是子命名空间中 GID 和 UID 的有效范围。
- 我不确定是否可以在容器配置中为每个容器定义不同的范围。例如,如果主机用户
因此,要点实际上是您需要确保容器的文件/文件夹所有者和组与配置匹配,而配置又必须是分别分配给user1
和的主机从属 GID/UID 的有效子集user2
。
例如,如果您使用的是 Bash,则可以使用$((expression))
for 算术表达式并将let
算术表达式分配给变量。如果您知道“内部”用户的基值(分别为 100000 和 200000)和 GID/UID,这将非常有用。
要点是:
- 这是可能的
- 要么能力
CAP_CHOWN
或者需要超级用户权限
这是一个可能需要更多磨练的脚本(例如:从根创建的容器迁移到非特权容器),但它对我来说适用于以下目的:
#!/usr/bin/env bash
function syntax
{
echo "SYNTAX: ${0##*/} <from-user> <to-user> <container-name>"
[[ -n "$1" ]] && echo -e "\nERROR: ${1}."
exit 1
}
# Checks
[[ -n "$1" ]] || syntax "<from-user> is not set"
[[ -n "$2" ]] || syntax "<to-user> is not set"
[[ -n "$3" ]] || syntax "<container-name> is not set"
[[ "$UID" -eq "0" ]] || syntax "${0##*/}" "You must be superuser to make use of this script"
# Constants with stuff we need
readonly USERFROM=$1
readonly USERTO=$2
shift; shift
readonly CONTAINER=${1:-*}
LXCLOCAL=".local/share/lxc"
readonly HOMEFROM=$(eval echo ~$USERFROM)
readonly HOMETO=$(eval echo ~$USERTO)
readonly LXCFROM="$HOMEFROM/$LXCLOCAL"
readonly LXCTO="$HOMETO/$LXCLOCAL"
readonly GIDBASEFROM=$(awk -F : "\$1 ~/$USERFROM/ {print \$2}" /etc/subgid)
readonly UIDBASEFROM=$(awk -F : "\$1 ~/$USERFROM/ {print \$2}" /etc/subuid)
readonly GIDSIZEFROM=$(awk -F : "\$1 ~/$USERFROM/ {print \$3}" /etc/subgid)
readonly UIDSIZEFROM=$(awk -F : "\$1 ~/$USERFROM/ {print \$3}" /etc/subuid)
readonly GIDBASETO=$(awk -F : "\$1 ~/$USERTO/ {print \$2}" /etc/subgid)
readonly UIDBASETO=$(awk -F : "\$1 ~/$USERTO/ {print \$2}" /etc/subuid)
readonly GIDSIZETO=$(awk -F : "\$1 ~/$USERTO/ {print \$3}" /etc/subgid)
readonly UIDSIZETO=$(awk -F : "\$1 ~/$USERTO/ {print \$3}" /etc/subuid)
unset LXCLOCAL
# More checks
[[ -d "$LXCFROM" ]] || syntax "Could not locate '$LXCFROM'. It is not a directory as expected"
[[ -e "$LXCTO" ]] && syntax "Destination '$LXCTO' already exists. However, it must not"
for i in GIDBASEFROM UIDBASEFROM GIDBASETO UIDBASETO; do
(($i > 0)) || syntax "Could not determine base/offset of subordinate UID/GID range"
done
for i in GIDSIZEFROM UIDSIZEFROM GIDSIZETO UIDSIZETO; do
(($i > 0)) || syntax "Could not determine length of subordinate UID/GID range"
done
echo "Going to migrate container: $CONTAINER"
echo -e "\tfrom user $USERFROM ($HOMEFROM): subUID=${UIDBASEFROM}..$((UIDBASEFROM+UIDSIZEFROM)); subGID=${GIDBASEFROM}..$((GIDBASEFROM+GIDSIZEFROM))"
echo -e "\tto user $USERTO ($HOMETO): subUID=${UIDBASETO}..$((UIDBASETO+UIDSIZETO)); subGID=${GIDBASETO}..$((GIDBASETO+GIDSIZETO))"
while read -p "Do you want to continue? (y/N) "; do
case ${REPLY:0:1} in
y|Y)
break;
;;
*)
echo "User asked to abort."
exit 1
;;
esac
done
# Find the UIDs and GIDs in use in the container
readonly SUBGIDSFROM=$(find -H "$LXCFROM" -printf '%G\n'|sort -u)
readonly SUBUIDSFROM=$(find -H "$LXCFROM" -printf '%U\n'|sort -u)
# Change group
for gid in $SUBGIDSFROM; do
let GIDTO=$(id -g "$USERTO")
if ((gid == $(id -g "$USERFROM"))); then
echo "Changing group from $USERFROM ($gid) to $USERTO ($GIDTO)"
find -H "$LXCFROM/$CONTAINER" -gid $gid -exec chgrp $GIDTO {} +
elif ((gid >= GIDBASEFROM )) && ((gid <= GIDBASEFROM+GIDSIZEFROM)); then
let GIDTO=$((gid-GIDBASEFROM+GIDBASETO))
echo "Changing group $gid -> $GIDTO"
find -H "$LXCFROM/$CONTAINER" -gid $gid -exec chgrp $GIDTO {} +
else
echo "ERROR: Some file/folder inside '$LXCFROM/$CONTAINER' has a group not assigned to $USERFROM (assigned subordinate GIDs)."
echo -e "Use:\n\tfind -H '$LXCFROM/$CONTAINER' -gid $gid\nto list those files/folders."
exit 1
fi
done
# Change owner
for uid in $SUBUIDSFROM; do
let UIDTO=$(id -u "$USERTO")
if ((uid == $(id -u "$USERFROM"))); then
echo "Changing owner from $USERFROM ($uid) to $USERTO ($UIDTO)"
find -H "$LXCFROM/$CONTAINER" -uid $uid -exec chown $UIDTO {} +
elif ((uid >= UIDBASEFROM )) && ((uid <= UIDBASEFROM+UIDSIZEFROM)); then
let UIDTO=$((uid-UIDBASEFROM+UIDBASETO))
echo "Changing owner $uid -> $UIDTO"
find -H "$LXCFROM/$CONTAINER" -uid $uid -exec chown $UIDTO {} +
else
echo "ERROR: Some file/folder inside '$LXCFROM/$CONTAINER' has an owner not assigned to $USERFROM (assigned subordinate UIDs)."
echo -e "Use:\n\tfind -H '$LXCFROM/$CONTAINER' -uid $uid\nto list those files/folders."
exit 1
fi
done
mv "$LXCFROM/$CONTAINER" "$LXCTO/" || { echo "ERROR: failed to move to destination: ${LXCTO}/${CONTAINER}."; exit 1; }
除了 StackExchange 网络的许可条款外,我还将其放入公共领域。因此,无论出于何种目的,都可以重复使用和修改,但它没有任何保证,我不对其使用或滥用承担责任。
用法SYNTAX: lxc-reassign-userns.sh <from-user> <to-user> <container-name>
它假设find
、sort
、uniq
、awk
(mawk
并且gawk
应该可以工作)、id
、bash
、chown
等chmod
可用,并了解它正在使用的所有命令行开关。对于 Bashreadonly
和let
以及算术表达式,假设可以理解。 For find
is 假定+
是该操作的有效终止符-exec
。
该列表可能并不完整。
备份
是的,您可以在其他地方进行备份和恢复,只要您还相应地调整文件所有者和组即可。
但是,假设您使用类似的东西tar
,有一个警告:tar
将忽略套接字,因此$rootfs/dev/log
会引起问题 - 其他人也可能会产生类似的问题。
资源:
答案2
fuidshift
为此目的而创建。它似乎是LXD的一部分。
http://manpages.ubuntu.com/manpages/xenial/man1/fuidshift.1.html
答案3
编辑: 流体转移是最好的方法。在Ubuntu中,由于LXD从DEB包转换为snap,流体转移不再发货,这种情况也不会发生。你需要编译流体转移靠你自己。
然而,这可以很容易地完成(源代码的下载和编译几乎是自动完成的),请参阅https://github.com/lxc/lxc/issues/3186
--
您只需将包含 LXC 容器的目录从 user1 复制到 user2 并使用以下 python 代码来移动 UID 和 GID:
#!/usr/bin/python3
import os
import sys
uidmap_start = 100000
uidmap_size = 65536
gidmap_start = 100000
gidmap_size = 65536
def changeUidGidRecursive(path):
changeUidGid(path)
if os.path.isdir(path) and not os.path.islink(path):
for filename in os.listdir(path):
sub_path = os.path.join(path, filename)
changeUidGidRecursive(sub_path)
def changeUidGid(path):
stat_info = os.lstat(path)
uid = stat_info.st_uid
gid = stat_info.st_gid
new_uid = uid + uidmap_start
new_gid = gid + gidmap_start
if (new_uid > uidmap_end):
print("Info: New UID %d for \"%s\" would be out of range. Not changing UID." % (new_uid, path))
new_uid = uid
if (new_gid > gidmap_end):
print("Info: New GID %d for \"%s\" would be out of range. Not changing GID." % (new_gid, path))
new_gid = gid
if (new_uid != uid or new_gid != gid):
mode = stat_info.st_mode
os.chown(path, new_uid, new_gid, follow_symlinks=False)
new_mode = os.lstat(path).st_mode
# If necessary, restore old mode
if (new_mode != mode):
os.chmod(path, mode)
if __name__ == '__main__':
uidmap_end = uidmap_start + uidmap_size
gidmap_end = gidmap_start + gidmap_size
base_path = ''
if len(sys.argv) > 1:
base_path = sys.argv[1]
else:
print("Usage: %s <path>" % (sys.argv[0]))
sys.exit(1)
if not os.path.exists(base_path):
print("Error: Path \"%s\" does not exist" % (base_path))
print("Exiting")
sys.exit(1)
changeUidGidRecursive(base_path)
sys.exit(0)
您将需要适应uidmap_start
,gidmap_size
并且可能还uidmap_size
需要gidmap_size
适应您的需求。
我使用此 python 代码将特权 LXC 容器迁移到非特权容器。 python 代码的运行速度比 shell 脚本更快。