Welcome to KaliArch Blog’s documentation!

一、背景

1.1 问题:

同事反馈有可以鉴于目前几次大的公有云事故,腾讯云/阿里云两大公有云厂商尚且存在这样令人触目惊心的时刻,更何况其他厂商和我们的日常操作,有人的地方就有误操作,百分之一的风险但如果一旦发生就是100%的问题,虽SLA但或多或少存在影响,客户反馈如果阿里的A地域发生故障,例如地质灾害或不可控因素引发的ecs无法访问,用户数据都在云上无法操作情况下该如何,但考虑到不同地域灾备的高额度IT成本,想采用每天ecs的冷备。

1.2 思路:

在最大程度的降低IT成本,又想在不可控大规模地域性灾难面前做些什么,每天凌晨业务低峰期对ECS制作镜像,同时复制到其他的不同地域,如北京的镜像复制到上海,当北京整个region异常情况下,可利用复制在目标地域的ECS创建出来,在此抛砖引玉,后续可以将ecs在目标地域开出来并关机,归档删除之前的镜像,等等。同样可以将RDS备份也同样备份到异地OSS内,目前阿里已经有EBS非常方便的灾难情况下恢复RDS。利用此思路同意的适用于其他场景下。

二、代码

2.1 结构

image0 如果多个实例可同时写入配置文件,用,进行分割。

github地址

2.2 核心代码

> 配置文件

# 阿里云ak配置,建议采用子账户只授权ecs镜像操作
[common]
# 阿里云acccesskeyid
accessKeyId = LTAIhfXlcjyln6tW
# 阿里云accesssecret
accessSecret = GwfAMvR4K2ELmt76184oqLTVgRfAso
# log目录名称
logdir_name = logdir
# log文件名称
logfile_name = ecsoperlog.log

# ecs源地域配置信息段
#支持在华北 1、华北 2、华北 3、华北 5、华东 1、华东 2 和华南 1 地域之间复制镜像。涉及其他国家和地区地域时,可以 提交工单 申请
[source]
# 源地域实例regionid,可以参考:https://help.aliyun.com/document_detail/40654.html?spm=a2c1g.8271268.10000.5.5f98df25B98bhJ
s_RegionId = cn-shanghai

# 源实例id,可指定多个用,进行分隔
s_InstanceId =  i-uf661wb708uvqc9jyhem,i-uf661wb708uvqc9jyhel

# 源端制作镜像name
s_ImageName = api-source-image

# 源镜像描述信息
s_Description = api-source-image源镜像描述信息

# 镜像复制目的地域配置信息段
[destination]
# 目的地域实例regionid,
d_DestinationRegionId = cn-qingdao

# 复制过来的镜像名称
d_DestinationImageName = api-destination-image

# 复制过来的镜像描述信息
d_DestinationDescription = api-destination-image目的镜像描述信息

image操作(制作镜像->查看镜像制作状态->复制镜像)
   # 创建实例生成器
   def _get_Instance(self):
       for Instance in self.s_InstanceId_list.split(','):
           yield Instance

   def _create_image(self):
       """
       创建镜像
       :return:返回镜像id
       """
       s_timer = time.strftime("%Y-%m-%d-%H:%M", time.localtime(time.time()))
       request = CreateImageRequest.CreateImageRequest()
       request.set_accept_format('json')
       request.add_query_param('RegionId', self.s_RegionId)
       request.add_query_param('InstanceId', self.s_InstanceId)
       request.add_query_param('ImageName', self.s_ImageName + s_timer)
       request.add_query_param('Description', self.s_Description + s_timer)
       response = self.ecshelper.do_action_with_exception(request)
       self.logoper.info('创建镜像任务已提交,镜像id:%s' % json.loads(response)["ImageId"])
       print('创建镜像任务已提交,镜像id:%s' % json.loads(response)["ImageId"])
       return json.loads(response)["ImageId"]

def _describe_image(self,imageid):
       """
       查询image状态
       :param imageid:
       :return:
       """
       request = DescribeImagesRequest.DescribeImagesRequest()
       request.set_accept_format('json')
       request.add_query_param('RegionId', self.s_RegionId)
       request.add_query_param('ImageId', imageid)
       response = self.ecshelper.do_action_with_exception(request)
       # 进度 json.loads(response)['Images']['Image'][0]['Progress']
       self.logoper.info('镜像创建进度:%s' %json.loads(response)['Images']['Image'][0]['Progress'])
       # 镜像状态
       return json.loads(response)['Images']['Image'][0]['Status']


   #镜像复制
   def _copy_image(self,imageid):
       """
       镜像复制
       :param imageid:源镜像id
       :return: 复制成功后的镜像id
       """
       flag = True
       while flag:
           try:
               if self._describe_image(imageid) == 'Available':
                   flag = False
               else:
                   time.sleep(300)
           except Exception as e:
               pass
       print('镜像已经创建完成')
       d_timer = time.strftime("%Y-%m-%d-%H:%M", time.localtime(time.time()))
       request = CopyImageRequest.CopyImageRequest()
       request.set_accept_format('json')
       request.add_query_param('RegionId', self.s_RegionId)
       request.add_query_param('DestinationRegionId', self.d_DestinationRegionId)
       request.add_query_param('DestinationImageName', self.d_DestinationImageName + d_timer)
       request.add_query_param('DestinationDescription', self.d_DestinationDescription + d_timer)
       request.add_query_param('ImageId', imageid)
       response = self.ecshelper.do_action_with_exception(request)
       self.logoper.info('复制镜像任务已提交,镜像id:%s' % json.loads(response)['ImageId'])
       print('复制镜像任务已提交,镜像id:%s' % json.loads(response)['ImageId'])
       return json.loads(response)['ImageId']

三、测试

3.2 查看web控制台

> 源镜像

image2 > 添加了时间戳,方便查看

image3 > 目的地域镜像

image4

四、优化

  • 可以后续增加对指定天数的镜像进行归档删除

一、背景

  • 需求:目前遇到的客户需求为将腾讯云CDB备份文件自动上传到腾讯云COS内,在此抛砖引玉,还有很多类似的需求均可以采用此类方法解决,线下IDC数据文件备份至云端COS内,或根据文件下载地址url将文件上传至COS内。
  • 思路:首先获取到CDB的备份下载url,通过COS的API上传文件,大佬如有更好的方法欢迎一块讨论。

二、技术细节

  • COS:COS有API同时有SDK,这就很方便我们来通过Python对COS进行各类操作,COS SDK for Python

  • CDB:CDB有API但是CDB的查询备份下载没有对应的SDK,此时只能通过API来进行获取,腾讯云API的签名很复杂,要进行:构造参数字典->对dict排序->拼接sign->对sign编码->拼接完成最终url->完成调用,签名方法查询备份API

  • requirements:

    cos-python-sdk-v5==1.5.2
    requests==2.19.1
    tencentcloud-sdk-python==3.0.15
    urllib3==1.23
    
  • 文件目录结构 image0

三、代码

github地址

3.1 配置文件

# auth:kaliarch
# func:将腾讯云cdb备份文件上传至cos制定的bucket内
# python version:python3+
# cos version:v5
# https://console.cloud.tencent.com/cos5/bucket

# 腾讯云公共信息配置段
[common]
# 腾讯云 secretid
secret_id = AKIDMdjegcmoGxxxxxxxxxxxxxxxxxxxx
# 腾讯云 secretkey
secret_key = d5MRL4VoxyvlQvxxxxxxxxxxxxxx

# 腾讯云cos信息配置段
[cosinfo]
# cos所在地域
cos_region = ap-chengdu

# 腾讯云bucket名字(cos v5 bucket名称组成:bucket+appid)
bucket_name = xuel-test-bucket-125396xxxx

# 腾讯云cdb信息配置段
[cdbinfo]
# cdb实例id
cdb_instanceid = cdb-rqaxxxxx

# cdb所在地域
cdb_region = ap-shanghai

# cdb 日志备份类型,coldbackup(冷备),binlog(二进制日志)和slowlog_day(慢查询日志)
cdb_bak_type = coldbackup

# 日志文件信息配置段
[loginfo]
#日志文件目录名称
logdir_name = rds_to_cos
#日志文件名称
logfile_name = rdsbak_to_cos.log

3.2 CDB API核心操作代码

#构建字典
keydict = {
        'Action': self.cdb_action,
        'Timestamp': str(int(time.time())),
        'Nonce': str(int(random.random() * 1000)),
        'Region': self.cdb_region,
        'SecretId': self.secret_id,
        # 'SignatureMethod': SignatureMethod,
        'cdbInstanceId': self.cdb_instanceid,
        'type': self.cdb_bak_type
}
#字典排序
sorted(zip(keydict.keys(), keydict.values()))
#字符串拼接
sign_str_init = ''
for value in sortlist:
        sign_str_init += value[0] + '=' + value[1] + '&'
sign_str = 'GET' + self.cdb_api_url + sign_str_init[:-1]
return sign_str, sign_str_init
#获取签名串并编码
secretkey = self.secret_key
signature = bytes(sign_str, encoding='utf-8')
secretkey = bytes(secretkey, encoding='utf-8')
my_sign = hmac.new(secretkey, signature, hashlib.sha1).digest()
my_sign = base64.b64encode(my_sign)
parse.quote(my_sign)
#获取最终url
result_url = 'https://' + self.cdb_api_url + sign_str + '&Signature=' + result_sign

单独运行此模块可以得到以下信息: image1

3.3 COS SDK核心操作代码

#根据文件大小自动选择简单上传或分块上传,分块上传具备断点续传功能
with open(filename, 'wb') as localfile:
        localfile.write(requests.request('get', url).content)
# 进行上传
response = cos_client.upload_file(
        Bucket=self.bucket_name,
        LocalFilePath=filename,
        Key=filename,
        PartSize=partsize,
        MAXThread=maxthread
)
# 删除本地文件
if os.path.exists(filename):
        os.remove(filename)

3.4 日志记录核心代码

#创建目录
def create_dir(self):
        _LOGDIR = os.path.join(os.path.dirname(__file__), self.logdir_name)
        _TIME = time.strftime('%Y-%m-%d', time.gmtime()) + '-'
        _LOGNAME = _TIME + self.logfile_name
        LOGFILENAME = os.path.join(_LOGDIR, _LOGNAME)
        if not os.path.exists(_LOGDIR):
                os.mkdir(_LOGDIR)
        return LOGFILENAME
#定义日志文件
def create_logger(self, logfilename):
        logger = logging.getLogger()
        logger.setLevel(logging.INFO)
        handler = logging.FileHandler(logfilename)
        handler.setLevel(logging.INFO)
        formater = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        handler.setFormatter(formater)
        logger.addHandler(handler)
        return logger

四、测试结果

获取CDB下载链接 image2 完成上传查看COS文件 image3

五、总结

  • 优化:可以后期通过配合定时任务完成自动化任务
  • 扩展:源端:不仅仅局限于CDB备份文件,对于随便下载url,均可以上传到COS内。终端:终端也不仅局限于腾讯云COS,此思路方法也可用于其他云平台如阿里OSS,亚马逊Amazon S3,百度云BOS 等。