我有一个基于 ZFS 的 iSCSI SAN,它为许多 VM 服务器提供 ZVOL。今天,网络故障导致客户端上安装的所有 iSCSI 卷都进入 RO 状态。唯一的解决方法是关闭所有 iSCSI 卷并重新启动,通常需要运行 fsck 才能使 iSCSI 卷重新上线。好吧,fsck 决定彻底销毁其中一个卷。所以,看起来我无法修复 fsck 造成的混乱。
我读过很多关于在 ZFS 上恢复文件的文章,但在这种情况下,我处理的是 ZVOL,从某种意义上说,这要简单得多,但我还没有看到任何关于尝试回滚块设备内容的文章。有什么建议吗?
—短暂性脑缺血发作—
一些数据集详细信息:
Dataset zpool1/vm3 [ZVOL], ID 59, cr_txg 12078, 44.6G, 2 objects, rootbp DVA[0]=<6:6c2c4b1e00:200> DVA[1]=<7:487aa4b200:200> [L0 DMU objset] fletcher4 lz4 LE contiguous unique double size=800L/200P birth=7736596L/7736596P fill=2 cksum=4c78779ec:2049fb2de6c:6f2f6c4a44e9:1042484aee3ded
Deadlist: 1K (512/512 comp)
mintxg 0 -> obj 48
mintxg 1 -> obj 4157
Object lvl iblk dblk dsize lsize %full type
0 7 16K 16K 7.00K 16K 6.25 DMU dnode
dnode flags: USED_BYTES
dnode maxblkid: 0
Object lvl iblk dblk dsize lsize %full type
1 5 16K 8K 44.6G 200G 36.45 zvol object
dnode flags: USED_BYTES
dnode maxblkid: 26214399
Object lvl iblk dblk dsize lsize %full type
2 1 16K 512 0 512 100.00 zvol prop
dnode flags: USED_BYTES
dnode maxblkid: 0
microzap: 512 bytes, 1 entries
size = 214748364800
系统是CentOS 7.1
Linux san1srvp01 3.10.0-514.6.1.el7.x86_64 #1 SMP Wed Jan 18 13:06:36 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
没有相关的快照;我想这不言而喻。
我提出这个问题的原因和我所追求的与这篇文章有关,最大燃烧它深入研究了通过取证技术进行对象恢复。这当然依赖于对 ZFS 内部知识的了解。不过,我所见过的大部分内容都是关于回顾文件对象,这与实现原始块存储的对象非常不同,而且我几乎没有看到与 ZVOL 相关的内部内容。
即使我无法从技术上“回滚” fsck 所做的更改,但至少可以回滚并找到一些关键的原始块。考虑到 ZFS 的 COW 行为,这应该是可能的……而且我缺乏足够的知识,但我通常不会让这阻止我。
答案1
是的,可以做到。不过会比较乱。
https://gist.github.com/jshoward/5685757
由于链接已经失效,因此包括原始文件。
zfs_revert-0.1.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
#Script for reverting ZFS changes by destroying uberblocks
#Author: Martin Vool
#E-mail: [email protected]
#Version: 0.1
#Date: 16 November 2009
import time
import subprocess
import sys
import os
#Default blocksize
bs=512
#default total blocks (sorry programming in estonian :-/)
suurus=None
if len(sys.argv) > 2:
for arg in sys.argv:
arg=arg.split('=')
if len(arg) == 1:
file=arg[0]
elif arg[0] == '-bs':
bs=int(arg[1])
elif arg[0] == '-tb':
suurus=int(arg[1])
else:
print 'Usage: zfs_revert.py [-bs=n default:n=512 blocksize] \\n [-tb=n total block size in blocks] [file/device] You have to set -tb'
exit(1)
print int(bs)
if suurus == None:
print 'Total block size in blocks is undefined'
exit(1)
#make solaris use gnu grep.
if os.uname()[0] == 'SunOS':
grep_cmd='ggrep'
else:
grep_cmd='grep'
#to format program output
def formatstd(inp):
inp=inp.split('\n')
ret=[]
for line in inp:
columns=line.split(' ')
nc=[]
for c in columns:
if c != '':
nc.append(c)
ret.append(nc)
return ret
#read blocks from beginning(64mb)
a_count=(256*bs)
#read blocks from end (64mb)
l_skip=suurus-(256*bs)
print 'Total of %s blocks'%suurus
print 'Reading from the beginning to %s blocks'%a_count
print 'Reading from %s blocks to the end'%l_skip
#get the uberblocks from the beginning and end
yberblocks_a=formatstd(subprocess.Popen('sync && dd bs=%s if=%s count=%s | od -A x -x | %s -A 2 "b10c 00ba" | %s -v "\-\-"'%(bs,file, a_count,grep_cmd,grep_cmd), shell=True, stdout=subprocess.PIPE).communicate()[0])
yberblocks_l=formatstd(subprocess.Popen('sync && dd bs=%s if=%s skip=%s | od -A x -x | %s -A 2 "b10c 00ba" | %s -v "\-\-"'%(bs,file, l_skip,grep_cmd,grep_cmd), shell=True, stdout=subprocess.PIPE).communicate()[0])
yberblocks=[]
for p in yberblocks_a:
if len(p) > 0:
#format the hex address to decmal so dd would eat it.
p[0]=(int(p[0], 16)/bs)
yberblocks.append(p)
for p in yberblocks_l:
if len(p) > 0:
#format the hex address to decmal so dd would eat it and add the skipped part.
p[0]=((int(p[0], 16)/bs)+int(l_skip)) #we have to add until the place we skipped so the adresses would mach.
yberblocks.append(p)
print '----'
#here will be kept the output that you will see later(TXG, timestamp and the adresses, should be 4, might be less)
koik={}
i=0
for p in yberblocks:
if len(p) > 0:
if i == 0:#the first output line
address=p[0]
elif i == 1:#second output line
#this is the output of od that is in hex and needs to be reversed
txg=int(p[4]+p[3]+p[2]+p[1], 16)
elif i == 2:#third output line
timestamp=int(p[4]+p[3]+p[2]+p[1], 16)
try:
aeg=time.strftime("%d %b %Y %H:%M:%S", time.localtime(timestamp))
except:
aeg='none'
if koik.has_key(txg):
koik[txg]['addresses'].append(address)
else:
koik[txg]={
'txg':txg,
'timestamp':timestamp,
'htime': aeg,
'addresses':[address]
}
if i == 2:
i=0
else:
i+=1
keys = koik.keys()
keys.sort()
while True:
keys = koik.keys()
keys.sort()
print 'TXG\tTIME\tTIMESTAMP\tBLOCK ADDRESSES'
for k in keys:
print '%s\t%s\t%s\t%s'%(k, koik[k]['htime'],koik[k]['timestamp'],koik[k]['addresses'])
try:
save_txg=int(input('What is the last TXG you wish to keep?\n'))
keys = koik.keys()
keys.sort()
for k in keys:
if k > save_txg:
for adress in koik[k]['addresses']:
#wrtie zeroes to the unwanted uberblocks
format=formatstd(subprocess.Popen('dd bs=%s if=/dev/zero of=%s seek=%s count=1 conv=notrunc'%(bs, file, adress), shell=True, stdout=subprocess.PIPE).communicate()[0])
del(koik[k])
#sync changes to disc!
sync=formatstd(subprocess.Popen('sync', shell=True, stdout=subprocess.PIPE).communicate()[0])
except:
print ''
break
答案2
没有相关的快照;我想这不言而喻。
因此,如果没有与 zpool 和数据健康的时间相关的快照,您就无法轻松地采取任何补救措施或进行回滚。
答案3
正如 Tero Kilkanen 在评论中正确指出的那样,您需要在 zvol 仍然有效时对其进行快照,否则您的数据就会丢失。
背景信息:
快照可以从任何数据集(文件系统或卷 (zvol))创建,并且本身也是一个(只读、依赖)数据集。它们始终是原子的,因此您可以获得整个数据集在某个时间点的状态(尽管对于您的应用程序来说,在最坏的情况下,它可能看起来像是磁盘或系统的硬重置),并且可以确保您的数据完整性得到保留(至少对于同步写入而言,异步写入当然可能会被部分丢弃)。
在这方面,zvols 和文件系统之间的唯一区别是 - 由于每个快照始终引用整个数据集 - 您可以从文件系统快照中挑选要恢复的文件(将其与当前或较旧的数据混合),但您只能选择使用整个 zvol,因为它就像一个非常大的文件(理论上,您可以复制字节范围并自行合并它们,但这会非常不方便)。除了对数据的这种“视图”(文件与块)之外,行为是相同的。
答案4
查看 zpool import -T 选项。注意:这是在池上,而不是 zvol 上。也许您可以将 zvol 发送到新的 zpool,然后使用 import -T 将其通过不同的 txg 导入。