|
|
楼主 |
发表于 2022-8-4 13:39:30
|
显示全部楼层
1、前言! m+ H; V- B4 P9 S. a9 b
这里实验在使用cephfs时,如果cephfs的元数据损坏或丢失了,那该如何恢复出用户数据。下面就为大家演示下如何恢复。
4 x6 q& ~4 q8 `: A( f" |" |$ W2 O0 @& S3 ^: }" X- L
2、准备测试环境
6 R0 [1 ]' Q3 ^0 n5 v+ F2.1、准备测试集群2 r# F0 W( G: v) M6 C P, @" j) d: ]
我是基于L版本做的实验,J版也是可以的。测试环境如下:
3 J/ A9 o2 L( J. B4 a3 a1 [% E; I4 _3 w
[root@ceph05 ~]# ceph -v
7 e2 \4 r' Q% Hceph version 12.2.11 (26dc3775efc7bb286a1d6d66faee0ba30ea23eee) luminous (stable)
, y9 E B+ ], z) t4 n' J6 p1 u3 k0 I& S7 J/ r
[root@ceph05 ~]# ceph osd tree
: k# J1 s! T1 `# `ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF
& ~. s" S* F e# n# A& D-1 0.07397 root default
4 n- }( `" y0 E% e-3 0.03699 host ceph05
$ l# h4 Y6 U( M& v6 y 0 hdd 0.01799 osd.0 up 1.00000 1.00000
! q$ M/ [. R9 O" C 1 hdd 0.01900 osd.1 up 1.00000 1.00000 1 F2 f. f( U6 E! c" U- s
-5 0.03699 host ceph06
# {: W+ d- _* Z( W: k' j( k! t# c 2 hdd 0.01900 osd.2 up 1.00000 1.00000 & b" L0 r/ t& R. G
3 hdd 0.01900 osd.3 up 1.00000 1.00000
" r& p; O2 c/ X
3 w3 I9 }7 G$ O$ J[root@ceph05 deployceph]# ceph -s
t& Y1 W3 E' k- w8 g) l cluster:7 i/ G9 `) g3 q f; q& b
id: 176feab8-ca22-47bf-b809-202deac53c6f
/ n: t1 P0 w! j: u m. s health: HEALTH_WARN
6 N8 [1 b6 n. U+ K4 t/ o, M$ R crush map has straw_calc_version=0
6 M0 g- e, Y$ A( }& ?3 ^9 k+ |+ S/ _* e* G; C& z* u6 K
services:2 i& T8 D0 F/ J) R
mon: 1 daemons, quorum ceph05% ]9 M6 a9 X" V# v* m
mgr: ceph05(active)5 v D7 f* h( h: V# e
mds: cephfs-1/1/1 up {0=ceph05=up:active}
" u. U; ?. E4 C' z# I osd: 4 osds: 4 up, 4 in
8 X( |% X2 I( E2 d2 ?; P
! D9 ? n; i- F data:6 N# |7 r7 Y/ J' F% p
pools: 10 pools, 304 pgs5 p- l4 c8 H$ M) D/ z* k, M
objects: 918 objects, 2.60GiB
7 m z% m j; k, t7 N& B) n7 u usage: 6.70GiB used, 71.3GiB / 78.0GiB avail
+ W* T9 G) p* D$ Z& q+ K8 G# H pgs: 304 active+clean3 A; }) Z* O8 M0 f G, L0 b: V7 q
2.2、准备测试数据
0 r/ }, `5 N) n- ^) a挂载kc
; c9 F% r! c- B7 T; _. F* N4 |$ M- R- t7 {6 \; x
[root@ceph05 deployceph]# mount -t ceph 192.168.10.30:/ /cephfs
0 u: z; R& T; Z% Z& ]( _[root@ceph05 deployceph]# df -h|grep ceph' d5 m" K5 _9 N% s
···: k! Y3 G( B5 J, J( A
192.168.10.30:/ 78G 6.8G 72G 9% /cephfs
& {8 f4 o/ g: H···, j. U3 T6 B' C- o! n
写入数据(这里我写入了几个比较有代表性的文件类型:txt、jpg、png、pdf、word、excel)9 M# M5 @( T+ N& ~8 q
' }* r% C* f' l" l9 |) W4 U/ J
[root@ceph05 deployceph]# ll /cephfs/6 _! b: h5 o( C9 l% N
total 5912& D% C. h5 q$ m" M" E0 I6 q% k% C' E
-rw-r--r-- 1 root root 31232 Mar 15 12:18 111.doc
& g4 T; T- Z6 p. {4 @-rw-r--r-- 1 root root 20593 Mar 15 12:18 22.xlsx
) }9 q1 w% P4 b& J-rw-r--r-- 1 root root 12494 Mar 15 12:17 5be23a3eec2c0.png% G6 \. ^7 g6 }! j, |
-rw-r--r-- 1 root root 3189 Mar 15 12:17 cmap.txt
" S6 `7 f4 `6 ]/ F3 ]$ `-rw-r--r-- 1 root root 5985243 Mar 15 12:17 hello0.pdf1 i. b6 p4 Y& \, M% i* u0 P3 C
3、模拟故障% ?( X7 O9 M. e4 s, B5 H I
这里直接模拟元数据丢失的情况,删除metadata池里面所有的元数据对象:
. B9 e4 U* D' B! N$ I
" G+ h5 J5 O5 ?( J$ I% t[root@ceph05 deployceph]# rados -p metadata ls|xargs -i rados -p metadata rm {}0 S- Z' M: b" p' }# N" Z& K
[root@ceph05 deployceph]# ceph df+ R- s; K" [1 W$ {
GLOBAL:" U U" {# N. t5 f4 o/ \
SIZE AVAIL RAW USED %RAW USED
- T, @! T. i9 @$ w. N6 J' y 78.0GiB 71.3GiB 6.71GiB 8.60
; E/ b( K9 I0 V9 O1 ]! rPOOLS:, q; J# H5 _# E K7 P
NAME ID USED %USED MAX AVAIL OBJECTS
; P2 d! d# b$ E- S* G# c; ]/ D2 Y .rgw.root 1 1.09KiB 0 63.3GiB 4 0 g7 ?2 M8 D/ V9 H4 ?4 D
default.rgw.control 2 0B 0 63.3GiB 8 ' y' p) U$ x( d$ t
default.rgw.meta 3 720B 0 63.3GiB 5 . q: Y" P1 M. a# N
default.rgw.log 4 0B 0 63.3GiB 207
; [4 b7 w H# V) y& Z default.rgw.buckets.index 5 0B 0 63.3GiB 1
, }9 |9 }9 _% i) W9 x& ^ default.rgw.buckets.data 6 1.02KiB 0 63.3GiB 2 # I1 T# Y' n K e, w/ y
pool01 7 2.60GiB 3.94 63.3GiB 666
1 T. T$ U7 K. a) M. I$ \0 v rbd 8 36B 0 63.3GiB 4
8 d3 D5 w3 R* B$ k" v5 I% g. s metadata 17 0B 0 63.3GiB 0
) z0 b- w, S4 v; A! D data 18 5.77MiB 0 63.3GiB 6
7 U5 c7 X) Q" B8 j' a看到metadata池里面没有对象了已经,重启下mds看效果,因为mds里面会缓存元数据信息,所以要重启下mds:7 ]' z) ]' S |
/ L. F: w" U+ L# n& L3 H) u
[root@ceph05 deployceph]# systemctl restart ceph-mds@ceph05
1 C( z' o' J7 A5 B' k[root@ceph05 deployceph]#
! ~6 @2 o* A1 {[root@ceph05 deployceph]#
; R; x- W6 p" P: Y. L- c0 w& {[root@ceph05 deployceph]# ceph -s
) y! ^. |$ W% G cluster:
" k. g7 J- f5 I2 T/ j id: 176feab8-ca22-47bf-b809-202deac53c6f- {* V1 w" F$ R/ L8 ?
health: HEALTH_WARN: J5 h0 S) `2 e: z
1 filesystem is degraded. o/ I; w# m2 g5 v
1 filesystem has a failed mds daemon
+ _. U7 N6 Q0 L4 |! I# w( [ crush map has straw_calc_version=0- S, a( [& N* K7 h; W
' e. `: U5 I5 y2 n5 b4 l services:
% D, ?3 |9 s( ?+ I, K4 c9 z5 s8 F mon: 1 daemons, quorum ceph05% C* a4 g5 H# B* F) V
mgr: ceph05(active)5 e) ^) n2 p9 c1 [* n0 S
mds: cephfs-0/1/1 up , 1 failed7 S) [1 a z0 ~) F4 N3 T% w. e- o
osd: 4 osds: 4 up, 4 in
; N& A. i' L0 J! ^: w; w4 L1 {
data:
- @& r9 B' e* ]9 G s& } pools: 10 pools, 304 pgs
- V( E, { B' H: D4 C2 ]& |; B: [ objects: 905 objects, 2.60GiB& c+ f& T. ?6 f8 l) P& r4 l8 u9 ?
usage: 6.71GiB used, 71.3GiB / 78.0GiB avail
' F. |2 U; b" q' y2 J pgs: 304 active+clean
: _. f& G5 J- l4 z& c9 @/ J看到集群现在不正常了,访问kc里面的数据卡住,说明数据已经无法正常读取了。
# Q. Q* f& C9 _2 O2 @ ~9 Y- x* b. h7 D* B0 i& Q
4、开始恢复
$ v3 h& [# X8 [$ K! B使用我编写的py脚本(文末给出了源码)恢复,把脚本放到集群任意一台节点上执行:
& R# J; f! s+ S) P+ {
g6 W- {% ]. F. Z[root@ceph05 rcy]# python recovery_cephfs.py -p data
9 u# L7 B7 `8 u* H. q. l-p指定cephfs的数据池,运行完之后会在当前目录下产生两个文件夹和一个运行脚本的日志文件。
/ Z |0 K2 u" t0 w, E y/ f( z G! F5 X4 s
) R o6 _$ V: h' e) f6 V[root@ceph05 rcy]# ll5 B9 ]* G+ l% d& ?* d6 Z- `9 a
total 16
; Z' V! ?9 ~, k5 K) n' t-rw-r--r-- 1 root root 3826 Mar 15 13:57 recovery_cephfs.py# v. c9 j7 |; r: |5 _& h0 p5 e) c9 G
drwxr-xr-x 2 root root 120 Mar 15 13:57 recoveryfiles
" u, o6 v& @( N. k2 Q-rw-r--r-- 1 root root 4804 Mar 15 13:57 recovery.log
; L4 ^2 N+ u C$ b& Vdrwxr-xr-x 2 root root 4096 Mar 15 13:57 recoveryobjs) y) o, g! D8 _% P
查看恢复出来的文件在recoveryfiles文件夹下:
6 U" E, ]( |: ]( C9 }1 C* V3 g0 S: M y" ]
[root@ceph05 rcy]# ll recoveryfiles/
; k: U1 H! T/ u% x0 Y6 P8 @total 12364
% D5 ~5 P! s' [/ m& h _-rw-r--r-- 1 root root 5985243 Mar 15 13:57 10000000000-pdf |4 q& d" _, m) v7 a
-rw-r--r-- 1 root root 3189 Mar 15 13:57 10000000001-text
8 y( E' _. z( ^5 L/ u, Y* C3 M-rw-r--r-- 1 root root 12494 Mar 15 13:57 10000000002-png* `, n* D) ]$ v0 `+ b C% u
-rw-r--r-- 1 root root 31232 Mar 15 13:57 10000000003-text- E" ?# I, T+ a& f/ T
-rw-r--r-- 1 root root 20593 Mar 15 13:57 10000000004-excel
, k- C; |4 k# z% R# f$ ^" E文件名格式为”文件在cephfs里面的inode-该文件可能的类型“。恢复出来的文件名后面会给出该文件的类型。这样就可以使用合适的软件打开该文件来验证文件是否完整。
3 a& n9 U2 ^, o' j, T( c* c( o2 R- I4 R* @) ^ Z
5、总结5 m* b# V5 e7 a, N5 h. g6 D. Y
在cephfs文件系统的元数据完全损坏的情况下,只要数据池对象不丢失,就可以恢复出完整的数据。恢复的思路如下:
# t3 z3 A) p; y0 k5 E
% ^( Y) X( r$ g: `' C0 E获取数据池对象
6 Z' a* k1 `! g& R根据inode找到该文件的所有对象
, D& E, R; p" {0 x4 Q( x拼接对象
2 G5 Q0 M0 \% k" g! l3 {, u使用脚本注意事项:
/ s6 a7 {& A; e
5 z" w! a' w& ?' d" j现在的脚本在只加入了txt、jpg、png、pdf、word、excel这些文件类型的识别,需要其他的就需要自己加入到脚本里面了3 ~, v D* Q; v E
只适合副本池
8 F' K% \( L4 ?. u' o如果数据量特别大,不适合使用脚本,不过可以参考脚本的思路去一个一个文件恢复# ` I/ X9 Z- l4 e5 L/ _
6、脚本1 I) }, I/ I N
# coding: utf-8
: B4 T/ ^# K7 Ximport os
y. J8 @; ]2 @( aimport shutil
5 b8 o! S, q5 Q7 E8 r' @" Oimport json+ A1 ?, O: f; k* Y
import sys6 z/ a( y& K4 F8 V9 J
import subprocess- f9 o& x; n) D |3 L0 i
import copy
\! x8 N. k$ Q/ `% s# x) w# C' \import logging6 T* t T: N b1 v8 v
import argparse
$ h5 e) v T4 J5 F' W
( T& P0 g$ e5 S, i- H__auth__ = 'ypdai'
0 v( Z2 D o% r( J7 {5 ]7 M/ y6 l5 c3 q
SLEEP_INTERVAL = 1
( T+ I9 N( e8 I _2 `+ tlogging.basicConfig(filename='./recovery.log', format='%(asctime)s : %(levelname)s %(message)s',, [ _) ^3 S) Q/ m1 `) L% H
level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S')
0 t) l7 L6 U9 E% E
* Y' i* s: t+ w1 S& hBASE_DIR = os.path.dirname(os.path.abspath(__file__))8 m7 c9 e& D* q/ A, U4 c6 O
RECOVERY_OBJ_DIR = os.path.join(BASE_DIR, 'recoveryobjs')) b0 N6 p4 W2 s! l+ H! C+ n
RECOVERY_FILE_DIR = os.path.join(BASE_DIR, 'recoveryfiles')
! f9 R7 X- H+ r2 K" V+ x
4 G$ J% Y/ B5 W: [4 D6 A" I
5 T2 T7 T* X. i2 T- A8 Vdef exec_cmd(cmd):! x$ c9 j0 ]( D& v
"""# v# |0 F3 _6 V4 ]5 U
执行shell命令,并返回标准输出和执行状态码
! n# h! V3 n T& r2 k :param cmd:
' v% [7 [2 C5 X! d$ l! o3 e :return:( p/ G2 Q8 a0 _, q
"""
* O8 Q2 L0 j& E4 ~9 l- Z2 G5 p/ I logging.info('exec_cmd():: cmd: {}'.format(cmd)), @$ _" S: O" }# J
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)& J* d1 s$ r+ T' Q* x% Z9 ]3 j
p.wait()* b' _0 M% Q; P7 Y/ w2 [* q
out = p.stdout.read()
( O! U$ Z2 g r ?& e code = p.returncode
, `# B% [3 ]; @' H logging.info('exec_cmd():: cmd exec out: {}, code: {}'.format(out, code))
$ C( _* t$ W0 T, l4 ?- ~ return out, code
$ z# X) ^. G0 c9 ^6 f/ u3 G! g- E: b
9 Y: g* y8 O2 {5 r% r, e4 }9 F3 q
def prepare(pool_name):# O ?/ p$ N: N4 H: S" S$ R
"""1 w1 `5 Z0 k `8 J! Q( ~& ^/ j
准备恢复条件
& E- S2 h7 r8 ^5 t2 x* p7 w) l# m 1、检查所给pool name是不是cephfs的数据池
6 }2 m1 T! a2 H, |+ E 2、创建recoveryobjs和recoveryfiles文件加
4 }, k5 r6 u1 N; A2 \" ~4 h5 v :param pool_name:8 ~( `5 a" O% V$ i8 P0 G! b6 t
:return:
0 s6 ] H) o' ?- T """
' i7 S) w& N+ h cmd = 'ceph fs ls -f json-pretty'
0 o% x8 s* w/ S* U, B out, code = exec_cmd(cmd); t& p) O) q$ u$ A: S
out = json.loads(out)
^" q8 N4 s+ e- x# ~ for cnt in out:
5 _0 L E- b6 F4 R8 p- m if pool_name not in cnt.get('data_pools'):
8 _' {( |1 G5 f- P$ d7 E return False
# e& i3 A& M0 H: k2 {! O" x. U/ f9 l( W* c V
if os.path.isdir(RECOVERY_OBJ_DIR):
' h7 M4 J0 p! W6 O/ M0 U shutil.rmtree(RECOVERY_OBJ_DIR)3 K; U1 r3 m3 V1 W
os.mkdir(RECOVERY_OBJ_DIR)
/ O& _5 k/ h# V5 B
& u2 k3 H/ O* r9 c% q# y if os.path.isdir(RECOVERY_FILE_DIR):
! ~: H4 ]/ k2 w. | shutil.rmtree(RECOVERY_FILE_DIR)% t+ J9 ~ ~0 E& V& v0 {' b9 R
os.mkdir(RECOVERY_FILE_DIR)
* v% T7 o+ D5 y% H
" b; ~0 s+ G* g return True% |- U" }2 r1 C7 ]+ N
: `- ^% Z' s3 A1 T1 Z5 V y
+ F0 Q8 c3 K- Z- d$ o/ Z$ c, m
def get_file_type(file_path):: _# X: e2 A$ L+ A2 ~
cmd = 'file %s' % file_path
; D3 r6 n: r+ c3 ], E out, code = exec_cmd(cmd)3 g* l$ M7 W$ p a: J
out = out.split(':')[-1].lower()1 s+ E% ?0 b) {/ C! A) ]' C
3 ~9 E5 r, V X2 o, g" l/ L file_type = 'text'( l9 V: J3 `& x1 j* n& g. H5 z
if 'word' in out:1 Y$ m4 `: G# n; M, ~) z3 ~
file_type = 'word'
8 b- s% @" ?& s, ~ elif 'excel' in out:
2 |7 `6 D8 |% _* D file_type = 'excel'' o* U* c+ }" d: y$ y3 J
elif 'pdf' in out:4 j) a3 l4 T- A: I
file_type = 'pdf'( U: a# `2 v8 W- d" M! _1 c. `2 n" K
elif 'text' in out:
2 G3 K) s! @' x# k6 |3 C file_type = 'text'5 l: @" r$ f- u5 y" w8 T
elif 'jpeg' in out:3 o- V' V) X% B& `4 p. w- Q
file_type = 'jpg'
0 |, u8 u5 b* I' C elif 'png' in out:
* C: Q8 F- L, M: Y/ T: z7 E file_type = 'png'
, e1 x+ G' ^- J. `& n
/ Q' D; x, Y. M$ B return file_type# Q' B0 k! M( R+ P) }
$ \1 D% G* G. Q1 D# m6 j. N1 {/ q& |3 i8 r* v2 M
def do_recovery(pool_name):
. o0 }; U/ }! \. p, ^ """$ t. M% X! Y( |
具体执行恢复,大概恢复逻辑如下:/ W! p, {( C+ H& D1 b n* D; E. C
1、从数据池里面获取所有的数据对象8 f' V0 ]" J( p3 p6 r" b Z
2、找到每个文件的head对象,然后把数据这个文件的其他对象内容写入head对象里面
7 n: n% y0 C! ~ 3、根据head对象的文件类型,推测该文件的实际类型2 @8 k0 J/ M% C9 {
:param pool_name:
( c1 ]5 p& {8 e) f4 c :return:
" v$ k9 _/ X/ ~; U6 e( Z """% i5 s6 d& @8 |7 R8 ?2 v+ |% L" h
cmd = 'for obj in $(rados -p %s ls);do rados -p %s get ${obj} %s/${obj};done' % (
, z5 a9 P9 A4 q- E. E pool_name, pool_name, RECOVERY_OBJ_DIR)+ ^# f3 a; g- C+ v7 w
out, code = exec_cmd(cmd)
+ I& |1 g, [: i9 `6 Q% \" c* s, B4 e$ e( e
if code != 0:
# q" c3 q9 k6 M5 j9 i# Q& t! _% d" y logging.error('do_recovery():: get obj from rados failed.'), f& i8 V* P4 [6 L+ z- s+ E7 m' Y( w& v
return4 E$ f+ q( K# ^. a- G$ \4 y
/ H {( g( y, z8 h
cmd = 'ls %s' % RECOVERY_OBJ_DIR
8 F9 _* `0 S: k( w, ]/ A% v out, code = exec_cmd(cmd)
& z- i5 w" M4 B if code != 0:1 e1 g# H1 w8 O2 H& e
logging.error('do_recovery():: list obj failed.')
( u0 m2 D& b$ z8 v; u& `1 X) M return; M! g6 U7 [4 F/ }& j B
# {! o/ ? R: h: f; c+ O
done_lst = []
3 i/ P O2 p! i% m, m objects = out.split()" J, D; H: I3 D
for obj in objects:, E9 v9 Z2 n) y4 W
inode, number = obj.split('.')& k; g7 ^8 p# \+ j+ ]% J) k. l
if inode in done_lst:$ Q6 B! |# m. x8 l* H
continue8 c/ V" p% I1 O3 i7 [5 s5 x& p* Y
5 S+ l6 t; Y4 x0 \6 _ y) R2 X$ a8 n3 u
cmd = '''ls -l %s | awk '{print $NF}' | grep ^%s |sort''' % (RECOVERY_OBJ_DIR, inode)% a1 m5 a# x5 z4 W
out, code = exec_cmd(cmd)3 K& z0 y3 G6 o; o
files = out.split('\n')+ _) Q' u. B) h* G+ G3 |& f. G4 O0 j% t6 j
V- v/ q. k! r& G6 a" G& J3 _ head_file = files[0]
" r8 X7 P8 j$ K5 d2 {4 q file_type = get_file_type('%s/%s' % (RECOVERY_OBJ_DIR, head_file))" H/ p' r, W; v; l& |! ^7 q5 }1 \4 C
cmd = 'cp %s/%s %s/%s-%s' % (RECOVERY_OBJ_DIR, head_file, RECOVERY_FILE_DIR, inode, file_type)$ b: c V- T W X, ^
out, code = exec_cmd(cmd)0 _7 K9 p$ W% H& @6 H( ^$ a$ O
for f in files[1:]:
( }- D) X: E C" E; e. X if not f:% `. P( l! }3 L
continue
0 U; d- x, p% X5 P. ]9 R cmd = 'cat %s/%s >> %s/%s-%s' % (RECOVERY_OBJ_DIR, f, RECOVERY_FILE_DIR, inode, file_type)7 w f9 ^4 o, j i& L
out, code = exec_cmd(cmd)
6 W/ \: p) s+ K( ?' y+ Y8 k1 L; z% U. |$ N+ ]
done_lst.append(inode)
' \4 m0 ?4 X7 F7 I. T
, s n' k$ t; g( w( x
/ O% f: d* q, l2 n& |if __name__ == '__main__':
2 S" T& s r3 @2 { S parser = argparse.ArgumentParser()
. t# @# C* b2 ]9 k& ^ parser.add_argument('-p', '--pool', required=True, type=str, dest='pool',
: _# q; y. o) I& |8 [+ E c# } help='select given cephfs data pool by name')
: @3 w, z8 P& U( E) I2 ] args = parser.parse_args()7 p( G2 ?8 W& e" V, Y* Q7 A
1 P0 R! W! _! C1 t2 s, [0 l if not prepare(args.pool):8 T9 e+ E. I2 ^( H' _( S
logging.error('main():: invalid pool name.') X D1 E: P C3 J# t
sys.exit(1)5 n4 f+ g K2 A) {
1 _7 J6 j# E/ K; X+ w) @
logging.info('=== main():: recovery start')
6 ?- K& g8 x9 l! k" z' r do_recovery(args.pool)' y, N R- b9 [& v
logging.info('=== main():: recovery done') |
|