0%

[腾讯课堂、钉钉]在线网课视频直播回放下载离线播放

有些精品好课真的是百看不厌、教师精心准备、学习氛围也好。不过有些已购买的课程却只能在线观看…现在讲究的是一个环保、节能。如果能将已购买的课程离线下载好,那么会为各大平台节省多少带宽费用当然还有数据包在这世间重复的传输所浪费的电力资源。

本着环保、节能、减排的目的,开始了尝试对腾讯课堂网页版中自己购买了课程的视频回放进行下载。

然后顺便又看了下钉钉的回放。

github直达项目:https://github.com/yeefire/cloud-class-replay

腾讯课堂

由于没有使用客户端,在网页上观看。使用浏览器中的开发者工具来寻找请求。

tencent_class_safari_devnet-2020-12-28-00-30-43

可以看到请求了很多视频片段,文件拓展名为ts,GET请求。于是直接复制求请求连接然后丢在浏览器地址栏播放,结果不行,是加密的。

因为看到了ts,那么八九不离十使用的是M3U文件存储分段多媒体信息。

ts是日本高清摄像机拍摄下进行的封装格式文件,全称为MPEG2-TS。

M3U8是Unicode版本的M3U,用UTF-8编码。”M3U”和”M3U8”文件都是苹果公司使用的HTTP Live Streaming格式的基础,这种格式可以在iPhone和Macbook等设备播放。

寻找M3U

在请求中搜索m3u,出现了几个m3u8拓展文件,选择资源文件最大的那个m3u8文件,获取cURL请求或者其他方式将其下载到本地方便进一步分析。

现在找到了m3u文件,我们可以获取到这节课的所有分段视频了。

tencent_class_safari_search_m3u8-2020-12-28-00-48-19

分析腾讯课堂M3U文件

已经下载好了m3u8拓展文件,接下来打开文件进行分析!

可以看到腾讯课堂的每个分段视频是使用AES-128进行加密的,好在下载到的m3u8文件里给出了解密密钥的地址以及偏移量。

这下我们有了密钥和偏移量还有分段视频的请求参数(还不知道HTTP请求路径)

tencent_class_m3u8_file-2020-12-28-01-06-24

tencent_class_m3u8_check-2020-12-28-01-11-59

整理总结

现在可以尝试下载一个小的视频片段,不过目前还没有请求视频片段的完整路径,只有一个个的请求参数。这个好办,再回到浏览器中播放回放视频,观察浏览器开发者工具中的网络请求动态,找到’vxxxxxx.ts’请求,并查看获取该视频片段的完整HTTP请求路径。

发现和m3u8的请求路径与其相似。

m3u: https://xxxxxxxxxx.vod2.myqcloud.com/xxxxxxxxxxxxxx/b4e0xxxxxxxxxxxxxx7/drm/voddrm.xxxxxxxxxxxxx

ts: https://xxxxxxxxxx.vod2.myqcloud.com/xxxxxxxxxxxxxx/b4e0xxxxxxxxxxxxxx7/drm/v.fxxxx.ts?start=195027344&end=195666559&type=mpegtsxxxxxxxxxxxxx

在最后出现/斜杠位置前的所有请求路径都相同。并且斜杠后的请求参数正是m3u文件中的一个个分段视频的请求参数,看来仅仅需要简单的拼接就可以将这些分段视频下载好了。

那么现在我们有了全部分段视频的下载请求地址、解密算法、解密密钥及偏移量。有了这些就可以尝试下载分段视频并进行解密和合并了。

先尝试解密一个分段视频试试看:

with aiofiles.open(m3u8_encrypt_file, mode='rb') as f:
f = f.read()
content_video_part = AES.new(key, AES.MODE_CBC, iv).decrypt(f)
with aiofiles.open(dest_decrypt_file, mode='wb') as f:
f.write(content_video_part)

可以正常播放,没有问题。接下来下载全部的分段视频并解密,最后重新整合为一个mp4格式视频文件。剩下的交给脚本处理了!

腾讯课堂回放下载脚本

脚本使用异步进行请求下载分段视频和解密视频,尽可能的以最快的速度下载好全部的分段视频。

如果下载期间遇到网络波动,脚本可以自动重试下载。

若脚本意外停止,可以继续追加下载,不必全部重新开始下载分段视频。

使用方法:

  • 先安装依赖模块 pip3 install pycrypto m3u8 aiofiles requests_async
  • 命令行执行 python3 tencent_class_m3u8.py 这节课的名称 这节课的M3U文件请求地址(网址或者本地路径都可以)

例如: python3 tencent_class_m3u8.py 【Python进阶】Python-上午 https://1dada217.vod2.myqcloud.com/fdadadada3kmdkfsxxxxxxxxxxxxxxxxx

from Crypto.Cipher import AES
import requests_async as requests
import aiofiles
import m3u8
import os, sys
import asyncio

class_video_name = sys.argv[1]
m3u8_file_uri = sys.argv[2]
prefix_request_url = f'{m3u8_file_uri.rsplit("/", 1)[0]}/'


async def download_m3u8_video(index: int, suffix_url: str):
if not os.path.exists(f'{class_video_name}/downloads/{index}.ts'):
i = 0
while i < 3:
try:
download_video_ts = await requests.get(url=prefix_request_url + suffix_url, timeout=30)
with open(f'{class_video_name}/downloads/{index}.ts', "wb") as ts:
ts.write(download_video_ts.content)
print(f'[{class_video_name}]——已下载第 {index} 个片段/ 共 {len(playlist.files)} 个片段')
return
except requests.exceptions.RequestException:
print(f'[{class_video_name}]——下载超时,正在重新下载第 {index} 个片段/ 共 {len(playlist.files)} 个片段')
await asyncio.sleep(i)
i += 1


async def download_m3u8_all():
if not os.path.exists(class_video_name + '/downloads'):
os.makedirs(class_video_name + '/downloads')
download_async_list = [asyncio.create_task(download_m3u8_video(i, video_suffix_url))
for i, video_suffix_url in enumerate(playlist.files, 1)]
await asyncio.wait(download_async_list)

download_encrypt_list = [uri for uri in os.listdir(f'{class_video_name}/downloads') if uri[0] != '.']
if len(download_encrypt_list) == len(playlist.files): # 判断是否有漏下的分段视频没有下载
print(f'[{class_video_name}]——视频全部下载完成')
return download_encrypt_list
else: # 有部分视频在三次重试后依旧没有下载成功
print(f'[{class_video_name}]——下载过程中出现问题,正在重试...')
return await download_m3u8_all()


async def decrypt_m3u8_video(m3u8_encrypt_file_uri: str, key: bytes, iv: bytes):
decrypt_name = f'{m3u8_encrypt_file_uri.split("/")[-1].split(".")[0]}'
dest_decrypt_uri = f'{class_video_name}/decryption/{decrypt_name}.de.ts'
if not os.path.exists(dest_decrypt_uri):
async with aiofiles.open(m3u8_encrypt_file_uri, mode='rb') as f:
f = await f.read()
content_video_part = AES.new(key, AES.MODE_CBC, iv).decrypt(f)
async with aiofiles.open(dest_decrypt_uri, mode='wb') as f:
await f.write(content_video_part)
print(f'[{class_video_name}]——已解密第 {decrypt_name} 个片段/ 共 {len(playlist.files)} 个片段')


async def decrypt_m3u8_all():
if not os.path.exists(class_video_name + '/decryption'):
os.makedirs(class_video_name + '/decryption')
key = await requests.get(playlist.keys[0].uri)
key = key.content
iv = bytes(playlist.keys[0].iv, 'UTF-8')[:16]
decrypt_m3u8_list = [asyncio.create_task(decrypt_m3u8_video(f'{class_video_name}/downloads/{uri}', key, iv))
for uri in os.listdir(f'{class_video_name}/downloads') if uri[0] != '.'] # 忽略隐藏文件
await asyncio.wait(decrypt_m3u8_list)
print(f'[{class_video_name}]——视频全部解密完成')


def merge_m3u8_all():
download_decrypt_list = [uri for uri in os.listdir(f'{class_video_name}/decryption') if uri[0] != '.']
download_encrypt_list = [uri for uri in os.listdir(f'{class_video_name}/downloads') if uri[0] != '.']
if len(download_decrypt_list) != len(download_encrypt_list): # 判断是否有漏下的分段视频没有下载
print('解密分段视频出现问题,可能是受限于类Unix系统文件句柄数量限制导致脚本不能获取足够的文件句柄。\n '
'如果你是 Linux 或 Macos 请尝试在运行本脚本的终端内执行 "ulimit -n 5120" 命令,以解除255(Macos)/1024(Linux)数量限制')
return
with open(f'{class_video_name}/{class_video_name}.mp4', 'ab') as final_file:
print(f'[{class_video_name}]——开始拼接解密后的分段视频')
temp_file_uri_list = os.listdir(f'{class_video_name}/decryption')
temp_file_uri_list.sort(key=lambda x: int(x[:-6]))
for uri in temp_file_uri_list:
if uri[0] == '.': continue # 忽略隐藏文件
with open(f'{class_video_name}/decryption/{uri}', 'rb') as temp_file:
final_file.write(temp_file.read()) # 将ts格式分段视频追加到完整视频文件中
print(f'[{class_video_name}]——合成视频成功')


if __name__ == '__main__':
playlist = m3u8.load(m3u8_file_uri, verify_ssl=False)
del playlist.files[0] # 第一个文件为视频密钥,忽略这个文件。
asyncio.run(download_m3u8_all())
asyncio.run(decrypt_m3u8_all())
merge_m3u8_all()
print(f'[{class_video_name}]——视频文件:{os.getcwd()}/{class_video_name}/{class_video_name}.mp4')

钉钉

钉钉回放下载更简单,之后将腾讯课堂回放的脚本稍作删减就可以用于钉钉回放下载。

Ceph块设备 对RBD块设备操作LVM创建PV时报错

当映射好了RBD映像中后,要在其上创建LVM逻辑卷,在执行pvcreate时出错。报错如下:

[root@ceph-master ceph]# pvcreate /dev/rbd0
/dev/sdd: open failed: No medium found
Device /dev/rbd0 excluded by a filter.

可以看到执行创建PV时被过滤器拦截掉了,这是因为默认情况下LVM不支持rbd设备类型,那么在LVM过滤器配置中手动添加RBD类型即可。

调试模式查看详细信息:

# pvcreate -vvvv /dev/rbd0 &> /tmp/out
# less /tmp/out
....
#filters/filter-type.c:27 /dev/rbd0: Skipping: Unrecognised LVM device type 252
....

查看设备类型ID

cat /proc/devices

可以找到rbd设备类型ID编号为252,记住它后接下来在LVM过滤器配置文件中添加它。

修改LVM过滤器配置文件

vim /etc/lvm/lvm.conf

找到types参数,将rbd和252修改为如下配置:

...
# Configuration section devices.
# How LVM uses block devices.
devices {
...
# Configuration option devices/types.
# List of additional acceptable block device types.
# These are of device type names from /proc/devices, followed by the
# maximum number of partitions.
#
# Example
types = [ "rbd", 252 ]
#
# This configuration option is advanced.
# This configuration option does not have a default value defined.
...

现在尝试重新添加rbd设备作为PV:

pvcreate /dev/rbd0

现在可以成功在RBD块设备上执行创建PV操作了。

Ceph 映射RBD块设备

前提准备

你需要有一个运行着的Ceph集群,并且已创建好了一个Pool池,以此来创建新的RBD映像。

Pool池名称:rbd

Namespace命名空间名称:42team

要创建的RBD镜像名:42team.dev.yeefire.com.100G.img

新建块设备

创建块设备首先要创建Pool池,关于Pool池的创建如果你还不知道的话可以先看看这篇文章:OSD与Pool池的常见操作及管理

创建Pool池:ceph osd pool create rbd 16 16

新创建的Pool池如果要用于RBD映像存储的话最好先对其初始化并对这个Pool池设置rbd应用:

  • 初始化Pool池用于RBD存储:rbd pool init rbd
  • 为rbd池设置app应用,并标记rbd应用:ceph osd pool application enable rbd rbd

创建命名空间(非必要操作)

命名空间的存在是方便对一个存储池Pool进行更细化的用户访问控制,这样可以少创建一些存储池,使用池中命名空间来对用户进行隔离。

如果你想了解如何使用cephx认证配合命名空间对用户限制访问池中资源,请阅读:Ceph用户管理

为rbd池创建名为42team的命名空间:namespace create -p rbd --namespace 42team

创建RBD映像

到这一步为止,你已经做了如下操作:

  • 有一个正常状态的Ceph存储集群
  • 创建了一个名为rbd的存储池
  • 将rbd存储池的application应用设置为rbd
  • 可选:(为rbd池创建名为42team的命名空间)
  • 可选:(创建一个cephx认证用户,并将这个用户的osd能力限制在rbd池中的42team命名空间内)

接下来开始创建一个RBD映像

rbd create -p rbd --namespace 42team --size 100G 42team.dev.yeefire.com.100G.img

查看创建的rbd映像

rbd ls -p rbd --namespace 42team

映射RBD映像到内核模块

用 rbd 把映像名映射为内核模块。必须指定映像名、存储池名、和用户名。若 RBD 内核模块尚未加载, rbd 命令会自动加载。

例如要将rbd池中42team命名空间的42team.dev.yeefire.com.100G.imgRBD映像映射到本机:

rbd device map -p rbd --namespace 42team --image 42team.dev.yeefire.com.100G.img

至此挂载RBD映像成功,如果要查看本机已挂载的RBD映像可以执行:

rbd device ls

Nginx反向代理下载传输超过1G大文件时断开问题

问题描述

42Team社团上线下载站,使用Nginx反向代理为用户提供服务。问题的现象是当用户下载文件超过1G大小时出现断开下载连接的情况导致下载失败。

问题分析

一开始以为是Flask的流传输出了问题,调试并跟踪代码后并没有发现问题,而且直接在本地代码调试下载大文件不会出现断开情况。那么开发环境与生产环境中只相差了中间有一层Nginx反向代理,那么初步问题定位到Nginx的反向代理配置上。

经过在网上进行搜索“反向代理下载大文件失败断开连接”等关键字后找到了一篇其他人写的博文,遇到过类似的问题。

发现可能是因为超时的原因导致,因为反向代理服务器和部署下载站服务的服务器之间的网络传输速度和磁盘性能非常好,所以他们之间传递1G以上的大文件仅仅有几秒的时间,而Nginx反向代理服务器将文件传输到用户端可能需要数分钟或数十分钟,由于这之间的时间差非常大,所以超过了Nginx的默认连接超时时间,导致此问题发生

问题解决

禁用缓存

禁用缓存,客户端的每次清求都转发到被代理服务器,做法是在代理服务器的Nginx配置里面添加:

proxy_pass http://172.17.8.88:5050/;
proxy_redirect default;
proxy_buffering off;

加大Nginx服务器与另一服务器之间的超时等待时间

keepalive_timeout 15;
send_timeout 3600;

Nginx反向代理导致大文件下载失败

Ceph块设备

块是一个字节序列(例如,一个512字节的一块数据),基于块的存储接口是最常见的存储数据方法,他们基于旋转媒体,类似于硬盘、CD、软盘、甚至传统的磁带。无处不在的块设备接口使得虚拟块设备成为与Ceph这样海量存储系统交互的理想之选。

Ceph块设备是瘦接口、大小可以调整且数据被条带化存储在Ceph集群内的多个OSD上。Ceph块设备均衡多个RADOS的能力,快照、复制和一致性,Ceph的RADOS块设备用内核模块或librbd库与各个OSD交互。

ceph-block-rados-2020-08-03

Ceph 块设备靠无限伸缩性提供了高性能,如向内核模块、或向 abbr:KVM (kernel virtual machines) (如 QEMU 、依赖 libvirt 和 QEMU 的 OpenStack 和 CloudStack 云计算系统都可与 Ceph 块设备集成)。可以用同一个集群同时运营 Ceph RADOS 网关、 CephFS 文件系统、和 Ceph 块设备。

块设备的基本操作

rbd命令可以用于创建、罗列、自检和删除块设备的映像。当然也可以对映像进行克隆、创建快照并支持回滚等。

创建一个块设备存储池

在管理节点上,用ceph命令创建一个Pool池。了解更多关于Pool池可以访问OSD与Pool池的常见操作及管理

ceph osd pool create rbd 64 64

成功创建一个Pool后使用rbd工具再对这个Pool池进行初始化用于RBD。

rbd pool init <pool-name>

此处将新创建的rbdPool池进行初始化

注意:在接下来使用rbd的命令时如果需要填写<pool-name>参数的地方为空没有填写,那么默认会查找Pool池名为rbd并使用它。

创建块设备用户(非必须)

如果不指定用户的话,使用rbd命令会默认使用admin管理员访问Ceph集群,admin的用户权限最大,不应该分配给其他机器。所以最好创建一个权限尽可能小的用户访问块设备池。

osd 'allow {access-spec} [{match-spec}] [network {network/prefix}]'

osd 'profile {name} [pool={pool-name} [namespace={namespace-name}]] [network {network/prefix}]'

如果要了解更多Ceph用户有关信息,可以访问阅读Ceph用户管理

创建块设备映像

如果想要将块设备添加到某一节点,你需要先在Pool中创建一个映像文件。

rbd create --size {megabytes} {pool-name}/{image-name}

例如,在rbd这个Pool池中创建一个名为42team-webnode.img,大小为20G的一个映像:

rbd create --size 20G --pool rbd --image 42team-webnode.img

罗列磁盘映像

要罗列rbdPool存储池中的全部映像,使用下面的命令:

rbd ls {poolname}

{poolname}如果不填写,则默认使用rbd存储池。

检索映像信息

使用下面的命令来检索某一个映像的信息:

rbd info {pool-name}/{image-name}

{poolname}如果不填写,则默认使用rbd存储池。

rbd info rbd/42team-webnode.img

返回结果:

[root@vm1 ~]# rbd info rbd/42team-webnode.img
rbd image '42team-webnode.img':
size 20 GiB in 5120 objects
order 22 (4 MiB objects)
snapshot_count: 0
id: 17382bc345839
block_name_prefix: rbd_data.17382bc345839
format: 2
features: layering, exclusive-lock, object-map, fast-diff, deep-flatten
op_features:
flags:
create_timestamp: Mon Aug 3 11:03:56 2020
access_timestamp: Mon Aug 3 11:03:56 2020
modify_timestamp: Mon Aug 3 11:03:56 2020

调整块设备映像大小

Ceph块设备是瘦接口设备,只有真正写入了数据后才开始占据物理空间,直到限制到它最大的存储大小,他们的最大存储容量就是设置的--size选项的值。如果需要增加或减少Ceph块设备的存储最大尺寸,使用下面的命令:

缩小映像最大尺寸

如果要缩小块设备的映像最大存储尺寸,需要添加--allow-shrink选项,才允许缩小映像尺寸。

rbd resize -p rbd --image 42team-webnode.img -s 5G --allow-shrink

如果已使用的存储尺寸大于要缩减之后的尺寸,强行缩减块设备映像尺寸会导致数据丢失!

扩大映像最大尺寸

rbd resize -p rbd --image 42team-webnode.img -s 25G

删除块设备映像

你可以直接将块设备中的映像删除,或者先将他们移动到垃圾桶中,等待一段时间删除或者手动进行清理。

直接删除块设备映像

rbd rm {pool-name}/{image-name}

rbd rm 42team-webnode.img

{poolname}如果不填写,则默认使用rbd存储池。

延期删除映像

延期删除可以现将要删除的映像存放到垃圾池中,即便他有快照或者正被克隆的镜像引用者也可以被放入到垃圾桶池中,但是不能将他们从垃圾池中删掉。

你可以用 –expires-at 设置延期时间(默认为 now ),并且,它的延期时间没到的话是不能删除的,除非你用 –force 选项。

rbd trash mv {pool-name}/{image-name}

rbd trash mv rbd/42team-webnode.img

rbd trash mv restore-42team-webnode.img --expires-at "20200805"

之后可以通过rbd trash ls --long查看放入到垃圾池的映像有哪些,如果设立日期保护的话还可以看到保护到期的时间。

还原块设备映像

当你突然醒悟不该放弃某个映像时,你当然可以从垃圾池中将映像还原!但是前提时你将它放入到了垃圾池而不是直接删除掉他们。

rbd trash restore {pool-name}/{image-id}

在还原映像时需要使用image-id进行还原。可以使用rbd trash ls查看image映像id。

[root@vm1 ~]# rbd trash ls
17382bc345839 42team-webnode.img
[root@vm1 ~]#

rbd trash restore 17382bc345839 --image restore-42team-webnode.img

在还原的同时,还可以指定--image选项对垃圾池内的映像重命名。

彻底删除在垃圾池中的映像

rbd trash rm {pool-name}/{image-id}

使用rbd trash rm命令可以将在垃圾池中的映像彻底删除,如果你设置了延期时间–expires-at ,那么没到延期时间内直接删除是不可以的,除非使用 –force 选项强制删除。

拍摄镜像快照

快照是某映像在一个特定时间点的一份只读副本。Ceph块设备的一个高级功能是可以为映像创建快照来保留历史。Ceph也支持分层快照,这样可以更快速的的克隆一个映像(如VM映像)。Ceph的快照功能还支持rbd命令和多种高级接口,包括 QEMU 、 libvirt 、 OpenStack 和 CloudStack

因为RBD不关心文件系统,如果没有与map挂载着的计算机协调的话,快照就是crash-consistent的。所以,在拍摄快照前应该将这个映像的I/O操作暂停,如果这个映像还包含文件系统,在拍摄这个包含文件系统的映像前系统还必须处于一致性状态,否则就要用fsck来修复它了…

可以使用fsfreeze命令冻结I/O。对于虚拟机来说,qemu-guest-agent 可在创建快照时自动冻结文件系统。

ceph-snap-ins-2020-08-06

快速操作快照

接下来的过程演示了如何用 rbd 命令创建、罗列、和删除快照。

创建快照

rbd snap create命令创建快照,需要声明存储池名和映像名。

rbd snap create {pool-name}/{image-name}@{snap-name}

如为rbd/5G.img拍摄快照:

rbd snap create 5G.img@2008050810

罗列快照

要列出某一个映像的快照,需要指定存储池名和映像名。

rbd snap ls {pool-name}/{image-name}

例如:

rbd snap ls 5G.img

[root@vm1 ~]# rbd snap ls 5G.img
SNAPID NAME SIZE PROTECTED TIMESTAMP
4 2008050810 5 GiB Wed Aug 5 08:10:55 2020

回滚快照

要用rbd回滚到某一快照,使用rbd snap rollback命令。回滚快照时要求该映像不能处于map状态!不应该有任何资源占用这个映像。

rbd snap rollback {pool-name}/{image-name}@{snap-name}

例如:

rbd snap rollback 5G.img@2008050810

把映像回滚到一快照的意思是,用快照中的数据覆盖映像的当前版本,此过程花费的时间随映像尺寸增长。从快照克隆要快于回滚到某快照。这也是回到先前状态的首选方法。

删除快照

删除某映像的一个快照

使用rbd snap rm命令删除某一映像的快照:

rbd snap rm rbd/5G.img@2008050810

删除后可以再查看一下当前快照,检查是否成功删除:

rbd snap ls rbd/5G.img

删除某映像的全部快照

当你要删除一个映像的全部快照时,使用rbd snap purge命令:

rbd snap purge {pool-name}/{image-name}

rbd snap purge rbd/5G.img

克隆快照映像

可以克隆一个映像,在原有映像基础之上建立新的分支。不过需要了解分层的概念,想要克隆一个快照必须先创建快照,并且将其保护,之后才能克隆快照。

了解分层

ceph-clone-2020-08-06

Ceph 块设备的分层是个简单的过程。你必须有个映像、必须为它创建快照、必须保护快照,执行过这些步骤后,你才能克隆快照。

克隆出的映像包含到父快照的饮用、存储池ID、映像ID和快照ID。包含存储池ID意味着你可以把存储池内的快照克隆到别的存储池中。

分层可以应用于以下方面:

  • 映像模版:块设备分层的一个常见用法是创建一个主映像及其快照,并作为模版以供克隆。例如管理员会创建一个Linux发行版的映像,之后周期性的升级维护(比如执行了dnf update操作之后等)。等映像文件稳定可用后,用户可以克隆任意快照。
  • 拓展模板:更高级的用法包括拓展映像模板,让它包含比基础映像更多的信息。例如,首先可以克隆一个基础映像,然后再此基础之上安装新的软件包(如数据库、博客平台、内容管理系统等),之后在此拓展上再次创建快照,拍下的快照可以像基础映像一样更新。
  • 模版存储池:块设备分层的一种用法是创建一个存储池,其中包含作为模版的主映像和那些模版快照。然后把只读权限分给用户,这样他们就可以克隆快照了,而无需分配此存储池内的写和执行权限。
  • 映像迁移/恢复:块设备分层的一种用法是把以存储池内的数据迁移或恢复到另一个存储池。

保护快照

克隆的映像要访问父快照。如果那个用户不小心删除掉了父快照,那么所有依赖这个父快照的克隆映像都会损坏。为了防止数据丢失,必须先保护快照,然后再当作父快照进行克隆。

rbd snap protect {pool-name}/{image-name}@{snapshot-name}

如下:

rbd snap protect 5G.img@202008051308

之后查看该映像的所有快照:

rbd snap ls 5G.img

[root@vm1 ~]# rbd snap ls 5G.img
SNAPID NAME SIZE PROTECTED TIMESTAMP
10 202008051308 5 GiB yes Wed Aug 5 13:08:03 2020
11 202008051309 5 GiB Wed Aug 5 13:08:05 2020
12 2020080513010 5 GiB Wed Aug 5 13:08:10 2020

可以看到刚刚被保护的快照,在此处有了标记,接下来尝试能否删除这个快照:

rbd snap rm 5G.img@202008051308

ceph-snap-protect-rm-2020-08-06

结果显而易见,当快照被保护的情况下是不允许被删除的。这样我们就可以在这个快照的基础上进行克隆全新的映像!

克隆映像快照

要克隆快照,你得指定父存储池、映像、和快照,还有子存储池和映像名。克隆前必须先保护快照,否则不允许克隆。

rbd clone {pool-name}/{parent-image}@{snap-name} {pool-name}/{child-image-name}

例如克隆我们刚刚保护的那个快照,并且把克隆后的映像叫做5G_new.img:

rbd clone 5G.img@202008051308 rbd/5G_new.img

你可以把一存储池中的映像的快照克隆到另一个存储池。例如,你可以把一存储池中的只读映像及快照当作模版进行维护,之后(比如映像调试完毕后)可以将其克隆到另一个存储池中进行发布使用。

现在,可以查看以下刚刚克隆的新映像的信息:

rbd ls --long

rbd info 5G_new.img

ceph-clone-ls-2020-08-06
ceph-clone-rbd-info-2020-08-06

罗列快照的后代

rbd children {pool-name}/{image-name}[@{snapshot-name}]

例如:

rbd children rbd/5G.img

取消快照保护

删除快照前,必须先需要快照保护。另外如果有其他的子映像使用着这个快照,那么你不能删除这个快照。除非你将引用这个快照的子映像执行拍平的操作。

rbd snap unprotect {pool-name}/{image-name}@{snapshot-name}

无法取消保护一个被克隆的子映像使用的快照,除非你将克隆后的映像拍平。
ceph-snap-unprotect-1-2020-08-06

拍平克隆的映像

克隆的映像一直保留着对父快照的引用,所以你无法取消保护一个被引用的父快照。如果你要从子克隆删除到父快照的这些引用,你可以把引用的父快照信息完整的复制到子克隆中,也就是拍平它。拍平克隆品的时间因快照尺寸而不同,要删除快照或者取消保护快照,必须将子映像拍平。

rbd flatten {pool-name}/{image-name}

例如:

rbd flatten rbd/5G_new.img

拍平子映像后,就不会再引用父快照的信息了。此时,你可以将父快照解除保护并删除。

从集群中删除OSD节点

使用cephadm部署的Ceph集群,再尝试从集群中删除OSD节点的时候遇到了各种问题……

既然自动化工具目前不太完善(或者说在文档里目前还没看到使用cephadm删除OSD节点的方法),那么就手动删除吧!记录一次使用cephadm搭建的集群如何删除OSD节点。当然使用其他方式部署的OSD要删除的话也是大同小异的。

ceph版本:ceph version 15.2.4 (7447c15c6ff58d7fce91843b705a268a1917325c) octopus (stable)

查看集群OSD状态

先查看集群内OSD的状态,并找到要删除的OSD节点。现在准备删除osd.4osd.5这两个节点,他们都在vm2这台主机上。

ceph osd tree

ceph-rm-osd-tree-2020-07-31

ceph osd dump

ceph-rm-osd-dump-2020-07-31

可以留意一下ID,之后便于确认ID所对应的块设备名(如:/dev/sdx或/dev/nvmex)

删除OSD节点节点

使用cephadm删除OSD节点

目前使用cephadm删除OSD节点后也需要手动的释放磁盘占用

ceph orch osd rm <svc_id>... [--replace] [--force]

要删除osd.4osd.5节点则执行:ceph orch osd rm 4 5

删除的过程需要一段时间,会重新分配集群内的PG,删除CRUSH图节点信息,删除这两个OSD所对应的用户和密钥环。

查看自动删除状态进度ceph orch osd rm status,如果长时间无响应或者依旧没有删除,可以开始尝试手动删除OSD节点

手动删除OSD节点

移除daemon守护进程

接下来的操作请确保你对整个集群拥有admin权限并且对要删除OSD节点的主机有root权限。

登录到拥有Ceph集群admin权限的主机,执行如下命令:

ceph orch daemon rm osd.4 osd.5 --force

返回结果如下,已经移除他们容器的守护进程:

Removed osd.4 from host 'vm2'
Removed osd.5 from host 'vm2'

此时查看集群中这两个OSD的状态:

要删除osd.4osd.5这两个节点的状态变成了DOWN。接下来就可以从crush表中将他们移除。

ceph-rm-osd-tree-2-2020-07-31

如果还没有使用cephadm管理集群,此处你可以登录到vm2这台主机中手动的将他们的systemd守护进程disable

systemctl disable --now  ceph-osd@4
systemctl disable --now ceph-osd@5

删除CRUSH图中所对应的OSD节点

删除CRUSH图中的OSD节点信息

ceph osd crush remove osd.4

ceph osd crush remove osd.5

执行成功返回结果:

removed item id 4 name 'osd.4' from crush map
removed item id 5 name 'osd.5' from crush map

此时再次手动检查集群内的OSD节点中是否还包含osd.4osd.5

已经从CRUSH图标中删除,此处已经看不到这两个OSD节点。

ceph-rm-osd-tree-3-2020-07-31

删除osd节点

ceph osd rm osd.4 osd.5

返回执行成功结果:removed osd.4, osd.5

ceph-osd-rm-2020-07-31

删除osd用户

ceph auth ls | grep ^osd -A4

ceph-auth-ls-2020-07-31

发现Ceph的OSD用户未被删除,残留在此处(会影响下一次添加OSD时,导致密钥环不匹配的错误)。执行如下命令删除:

ceph auth rm osd.4

ceph auth rm osd.5

删除OSD配置(如果有的话)

如果额外对OSD进行了额外配置的话需要手动删除当时的配置信息。登录到这台被删除OSD节点的主机上,修改配置文件:vim /etc/ceph/ceph.conf,将对该OSD节点额外配置的信息删除。

之后把更新过的ceph.conf文件要拷贝到其他集群主机的/etc/ceph/目录下。

解除Ceph对磁盘的占用

之后的操作都需要在移除OSD节点的主机(vm2)上通过root用户特权执行操作。

当删除了OSD之后,由于先前的配置,Ceph仍然对磁盘进行占用。需要移除这块OSD磁盘的DM状态,并之后对其格式化,这样才完整的手动移除一个OSD节点(磁盘)。

查询磁盘DM状态并移除编码

dmsetup status

dmset-status-2020-07-31

注意在一开始要留意的哪个ID,在这里用来识别要删除哪个磁盘的DM标记。

dmsetup remove ceph--14c75e65--a33e--459a--8bc7--c95add15d8a3-osd--block--d2af5a38--54b9--4112--9bbf--8ea313c673f1

dmsetup remove ceph--6ceb7c60--8b90--44fc--b159--34bd3d8c8e0f-osd--block--5f1d52fa--e50c--4915--b059--f672684ce571

执行完上面的命令后,再检查一下DM状态,发现已经成功被删除DM标记:

dmset-status-2-2020-07-31

wipefs格式化文件系统

在使用wipefs工具清除文件系统前,要找到所对应的块设备名称。

执行lsblk -f命令:

下面的图片中可以看到,nvme0n2nvme0n3这两个块设备已经没有了像nvme0n4下方的ID标记,所以可以判定nvme0n2nvme0n3和两块磁盘是我们移除的OSD节点所使用的磁盘。

fdisk-f-2020-07-31

接下来对nvme0n2nvme0n3两个块设备格式他们的文件系统,执行命令:

wipefs -a /dev/nvme0n2 /dev/nvme0n3

wipefs-2020-07-31

到此,完成的将OSD节点从集群中移除。你可以使用lsblk工具或稍后一段时间后在拥有集群admin权限的主机上执行ceph orch device ls命令查看这两个OSD节点所使用的磁盘是否是可以用的状态了。现在你可以将这两个磁盘取出或者在这两块磁盘上重新部署OSD节点…

fdisk-f-clean-2020-07-31

ceph-orch-device-ls-2020-07-31

Centos8配置 chrony NTP服务端及客户端

现在有一台服务器想作为NTP服务器,3台客户端想作为NTP服务器的客户端,保证客户端机器上的时间与服务器端保持同步。清单如下:

类型 IP地址
server 192.168.1.30
client 192.168.1.[31:33]

部署服务端chrony

ssh登录到server机器后放行防火墙、安装chrony服务、配置chrony服务器端。

放行防火墙端口

Centos8使用firewalld服务对防火墙进行管理。放行ntp服务(123/udp)

firewall-cmd --add-service=ntp --permanent && firewall-cmd --reload

安装chrony服务

默认情况下Centos8中已经安装好chrony的软件包。如果发现没有安装,使用下面的命令安装即可:

sudo dnf install chrony -y

配置chrony服务端

sudo vim /etc/chrony.conf

不习惯vi/vim编辑器的小伙伴可以替换为自己喜爱的编辑器如nano

把原有自带的上游NTP服务器地址删除。之后手动配置国内阿里云的NTP服务器地址。

server time1.aliyun.com iburst     #添加上游NTP服务器
server time2.aliyun.com iburst
server time3.aliyun.com iburst

allow 192.168.1.0/24 #允许IP端内的客户端通过这台服务器获取时间

配置无误后,重启chrony服务,并配置开机自启动:

systemctl restart chronyd.service && systemctl enable chronyd.service --now

使用ss -tlunp | grep chrony命令检查,发现chrony服务已经在监听123/udp端口,服务端配置成功。

配置chrony客户端

配置chrony客户端需要将三台客户端都安装chrony并且配置好他们的配置文件。如果说客户端数量较少可以手动配置,当数量为成百上千台机器时使用Ansible自动化配置是一个不错的选择。所以在这里有两种配置方法供大家参考,一种是普通手动配置,一种是使用Ansible-Galaxy角色配置。

手动配置chrony客户端

配置客户端同样的需要放行防火墙、安装chrony软件包、配置chrony客户端。

安装chrony软件包

dnf install chrony -y

修改客户端chrony配置文件

sudo vim /etc/chrony.conf

不习惯vi/vim编辑器的小伙伴可以替换为自己喜爱的编辑器如nano

# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
# pool 2.centos.pool.ntp.org iburst

# 把原有的NTP服务器地址注释掉或者直接删除。添加下面刚刚搭建好的NTP服务器地址。
server 192.168.1.30 iburst

注释默认的NTP服务器地址,之后添加上我们自己刚刚搭建好的NTP服务器地址即可完成chrony客户端的手动配置。

重启chrony客户端服务

重启chrony服务,并配置开机自启动:

systemctl restart chronyd.service && systemctl enable chronyd.service --now

查看同步状态

chronyc sources -v

[root@vm1 ~]# chronyc sources -v
210 Number of sources = 1

.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current synced, '+' = combined , '-' = not combined,
| / '?' = unreachable, 'x' = time may be in error, '~' = time too variable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* 192.168.1.30 3 6 17 16 +3424ns[ +110us] +/- 31ms

可以看到,输出结果里已经有我们搭建好的NTP服务器了。并且在服务器地址前有^*标记,证明已经正确同步时间。

至此一台客户端的chrony已经搭建完毕。剩下的两台同理部署即可。

使用Ansible自动化配置chrony客户端

相关Ansible基础默认你已经了解,不再赘述。如果你不了解Ansible请从Ansible文档入门学习。

mkdir Chrony && cd Chrony

配置ansible.cfg

vim ansible.cfg

[defaults]
inventory = inventory
remote_user = root
roles_path=./roles:/usr/share/ansible/roles:/etc/ansible/roles

[privilege_escalation]
become=True
become_method=sudo
beome_user=root
become_ask_pass=False

配置主机清单

vim inventory

[client]
192.168.1.3[1:3]

配置ssh免密登陆客户端

配置SSH免密登录

下载chrony角色

ansible-galaxy install ericsysmin.chrony

编写Playbok

vim playbook.yml

---
- name: Config Chrony Client
hosts: client
roles:
- role: ericsysmin.chrony
chrony_config_server:
- 192.168.1.30
...

执行结果如下,全部OK即为成功:

PLAY RECAP **************************
192.168.1.31 : ok=7 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
192.168.1.32 : ok=7 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
192.168.1.33 : ok=7 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0

之后可以登录几台机器进行手动验证,查看是否已经以NTP服务器保持同步。