天天看點

資源編排ROS之自定制資源(多雲部署AWS篇)背景目标準備工作測試驗證總結

背景

資源編排服務

(Resource Orchestration Service, 簡稱ROS)是阿裡雲提供的一項簡化雲計算資源管理的服務。您可以遵循ROS定義的模闆規範編寫資源棧模闆,在模闆中定義所需的雲計算資源(例如ECS執行個體、RDS資料庫執行個體)、資源間的依賴關系等。ROS的編排引擎将根據模闆自動完成所有資源的建立和配置,實作自動化部署及運維。

ROS資源編排接入了大量的阿裡雲資源,目前涉及38個服務,近200個資源,而且還在持續增長中。對于尚未提供的資源,或無法提供的資源或功能,ROS提供了

自定義資源

(

ALIYUN::ROS::CustomResource

)作為解決方案。如果您還不了解自定義資源,可以參考

資源編排ROS之自定制資源(基礎篇)

本篇為ROS多雲部署AWS篇。

其他進階篇:

多雲部署Terraform篇

目标

通過ROS成功部署AWS雲資源。推而廣之,使用類似的方法,可以對所有其他雲廠商的雲資源進行部署,進而達到多雲部署的目标。

準備工作

ROS模闆

為了簡化,我們使用如下模闆在ROS中進行部署。這個模闆建立了3個資源:

  • Service:FC(函數計算)的Service,用于建立Function。
  • Function:FC(函數計算)的Function,其代碼實作了在AWS CloudFormation管理堆棧的邏輯。模闆中,通過與ROS資源棧同一地域的OSS(對象存儲) Bucket和Object指定代碼。為了示例,這裡的BucketName為ros-demo,ObjectName為test-custom-resource-aws.zip。
  • TestAwsCloudFormationStack:自定義資源,用于傳遞參數和接收輸出。
    • 傳遞參數:包括AWS的AK資訊,地域資訊,CloudFormation堆棧資訊(包括名稱、模闆和參數)。用于測試的CloudFormation的模闆由于較為簡單,已内嵌到ROS模闆當中。為了友善示例,這個模闆隻建立了一個AWS::S3::Bucket資源。
    • 接收輸出:為了簡單,内嵌的CloudFormation模闆的輸出與自定義資源的輸出一緻。為了友善示例,輸出隻有一個,為AWS::S3::Bucket資源的ARN。
ROSTemplateFormatVersion: '2015-09-01'
Parameters:
  ServiceName:
    Type: String
    Default: test-service
  FunctionName:
    Type: String
    Default: test-function
  Timeout:
    Type: Number
    Default: 600
    MaxValue: 600
  AwsAccessKeyId:
    Type: String
    NoEcho: true
  AwsAccessKeySecret:
    Type: String
    NoEcho: true
  AwsRegionId:
    Type: String
    Default: us-east-1
Resources:
  Service:
    Type: ALIYUN::FC::Service
    Properties:
      ServiceName:
        Ref: ServiceName
  Function:
    Type: ALIYUN::FC::Function
    Properties:
      ServiceName:
        Fn::GetAtt: [Service, ServiceName]
      FunctionName: 
        Ref: FunctionName
      Handler: index.handler
      Runtime: python2.7
      Timeout:
        Ref: Timeout
      Code:
        OssBucketName: ros-demo
        OssObjectName: test-custom-resource-aws.zip
  TestAwsCloudFormationStack:
    Type: Custom::AwsCloudFormationStack
    Properties:
      ServiceToken:
        Fn::GetAtt: [Function, ARN]
      Parameters:
        AccessKeyId:
          Ref: AwsAccessKeyId
        AccessKeySecret:
          Ref: AwsAccessKeySecret
        RegionId:
          Ref: AwsRegionId
        Stack:
          Name: stack-by-ros
          Template: |
            AWSTemplateFormatVersion: '2010-09-09'

            Parameters:

              BucketName:
                Type: String

            Resources:
              MyBucket:
                Type: AWS::S3::Bucket
                Properties:
                  BucketName:
                    Ref: BucketName

            Outputs:
              MyBucketArn:
                Value:
                  Fn::GetAtt: [MyBucket, Arn]
          Parameters:
            BucketName: bucket-by-ros
      Timeout:
        Ref: Timeout
Outputs:
  AwsMyBucketArn:
    Value:
      Fn::GetAtt: [TestAwsCloudFormationStack, MyBucketArn]
           

函數代碼

模闆中的Function資源的主要實作代碼如下。這個FC函數的主要功能如下:

  • 按照ALIYUN::ROS::CustomResource的規範接受輸入,傳回輸出。
  • 按照在模闆中自定義的資源屬性,對AWS CloudFormation的堆棧進行管理。這是一個簡單的示例實作,使用AWS提供的boto3庫,對CloudFormation的堆棧執行操作(建立、更新、删除),并使用輪詢的方式等待其完成。
# -*- coding: utf-8 -*-

import time
import json
import urllib2
import logging

import six
import boto3


class AwsError(Exception):

    pass


class AwsStack(object):

    def __init__(self, client, logger, event):
        self._client = client
        self._logger = logger
        self._event = event

    def _get_stack_args(self):
        event = self._event
        client_token = '-'.join([event['StackId'], event['RequestId'],
                                 event['LogicalResourceId']])[:128]
        stack_info = event['ResourceProperties']['Stack']
        stack_name = event.get('PhysicalResourceId') or stack_info['Name']
        return (stack_name, stack_info['Template'],
                [dict(ParameterKey=k, ParameterValue=v)
                 for k, v in six.iteritems(stack_info['Parameters'])], client_token)

    def _wait(self, stack_id):
        self._logger.info('begin to wait for stack %s.', stack_id)
        while True:
            resp = self._client.describe_stacks(StackName=stack_id)
            stack = resp['Stacks'][0]
            status = stack['StackStatus']
            if status.endswith('_IN_PROGRESS'):
                time.sleep(3)
                continue
            self._logger.info('end to wait for stack %s: %s.', stack_id, stack)
            if status.endswith('_COMPLETE'):
                return {item['OutputKey']: item['OutputValue']
                        for item in stack.get('Outputs', ())}
            raise AwsError('Stack operation failed, status: {}.'.format(status))

    def create(self):
        stack_name, template_body, parameters, client_token = self._get_stack_args()
        resp = self._client.create_stack(
            StackName=stack_name,
            TemplateBody=template_body,
            Parameters=parameters,
            ClientRequestToken=client_token
        )
        self._logger.info('create stack: %s.', resp)
        stack_id = resp['StackId']
        return stack_id, self._wait(stack_id)

    def update(self):
        stack_name, template_body, parameters, client_token = self._get_stack_args()
        resp = self._client.update_stack(
            StackName=stack_name,
            TemplateBody=template_body,
            Parameters=parameters,
            ClientRequestToken=client_token
        )
        self._logger.info('update stack: %s.', resp)
        return self._wait(stack_name)

    def delete(self):
        stack_name, _, _, client_token = self._get_stack_args()
        resp = self._client.delete_stack(
            StackName=stack_name,
            ClientRequestToken=client_token
        )
        self._logger.info('delete stack: %s.', resp)
        self._wait(stack_name)

    @classmethod
    def from_event(cls, event, logger):
        res_props = event['ResourceProperties']
        session = boto3.Session(aws_access_key_id=res_props['AccessKeyId'],
                                aws_secret_access_key=res_props['AccessKeySecret'],
                                region_name=res_props['RegionId'])
        client = session.client('cloudformation')
        return cls(client, logger, event)


def handler(event, context):
    logger = logging.getLogger()
    event = json.loads(event)
    req_type = event['RequestType']
    aws_stack = AwsStack.from_event(event, logger)

    result = dict(
        RequestId=event['RequestId'],
        LogicalResourceId=event['LogicalResourceId'],
        StackId=event['StackId'],
    )
    try:
        aws_stack_id = aws_stack_outputs = None
        if req_type == 'Create':
            aws_stack_id, aws_stack_outputs = aws_stack.create()
        elif req_type == 'Update':
            aws_stack_outputs = aws_stack.update()
        else:
            aws_stack.delete()

        result['Status'] = 'SUCCESS'
        result['PhysicalResourceId'] = aws_stack_id or event.get('PhysicalResourceId')
        if aws_stack_outputs:
            result['Data'] = aws_stack_outputs
    except Exception as ex:
        result['Status'] = 'FAILED'
        result['Reason'] = str(ex)

    headers = {
      'Content-type': 'application/json',
      'Accept': 'application/json',
      'Date': time.strftime('%a, %d %b %Y %X GMT', time.gmtime())
    }
    req = urllib2.Request(event['ResponseURL'], data=json.dumps(result), headers=headers)
    resp = urllib2.urlopen(req)
    resp_content = resp.read()
    logger.info('response: %s %s', result, resp_content)
           

由于這個FC函數使用了非内置庫boto3,是以不内直接使用上述代碼,需要進行打包。具體可以參考

使用自定義的子產品

。大緻流程如下:

# 環境資訊:MAC, Python 2.7

mkdir /tmp/code
# 寫入函數代碼
cat > /tmp/code/index.py<<EOF
# 上述代碼内容
EOF
# 安裝依賴庫
pip install -t /tmp/code boto3
# 删除pyc檔案
find . -iname "*.pyc" | xargs rm -f


# 安裝fc sdk
pip install aliyun-fc2
# 把檔案夾壓縮成zip包
python -c "from fc2 import util; util.zip_dir('/tmp/code', '/tmp/test-custom-resource-aws.zip')"
           

然後把/tmp/test-custom-resource-aws.zip檔案上傳到OSS上即可。模闆中上傳到的OSS Bucket為ros-demo、Object為test-custom-resource-aws.zip,可按需調整,但要與ROS資源棧在同一地域。

示例中的壓縮包也可以通過

這個位址

進行下載下傳。

測試驗證

我們在ROS對上述模闆和代碼進行測試驗證。

建立資源棧。在模闆中,AwsAccessKeyId和AwsAccessKeySecret都被設定成了加密參數。

資源編排ROS之自定制資源(多雲部署AWS篇)背景目标準備工作測試驗證總結
資源編排ROS之自定制資源(多雲部署AWS篇)背景目标準備工作測試驗證總結

觀察資源清單,ALIYUN::ROS::CustomResource資源已成功建立。

資源編排ROS之自定制資源(多雲部署AWS篇)背景目标準備工作測試驗證總結

觀察輸出清單,擷取到AWS S3 Bucket的ARN。

資源編排ROS之自定制資源(多雲部署AWS篇)背景目标準備工作測試驗證總結

觀察AWS CloudFormation控制台,可以看到stack-by-ros堆棧,以及相關的AWS::S3::Bucket資源。

資源編排ROS之自定制資源(多雲部署AWS篇)背景目标準備工作測試驗證總結

總結

通過

)可以實作多雲部署,輕松實作一個入口,一鍵部署。