Python 3.0を使用してDevOpsのためのアリババクラウドFunction Computeを探る

この記事では、**Python 3.0**を使用したサンプルプログラムを用いて、**Alibaba Cloud Function Compute for DevOps**を探っていきます。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/alibaba-cloud-devops-cookbook-part-3-function-compute-with-python-3-0_593725)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

 [Alibaba Cloud Tech Share](https://www.alibabacloud.com/campaign/techshare?spm=a2c65.11461447.0.0.78f37ef6H54oV8) 執筆者、John Hanley。[Tech Share](https://www.alibabacloud.com/campaign/techshare?spm=a2c65.11461447.0.0.78f37ef6H54oV8)は、クラウドコミュニティ内で技術的な知識やベストプラクティスを共有することを奨励するAlibaba Cloudのインセンティブプログラムです。

[Alibaba Cloud Function Compute](https://www.alibabacloud.com/product/function-compute?spm=a2c65.11461447.0.0.78f37ef6H54oV8)は、イベント駆動型のサーバーレス・コンピューティング・プラットフォームであり、開発者はインフラストラクチャを管理する必要なくサービスを構築してデプロイすることができます。リソース管理、自動スケーリング、ロードバランシングをシームレスに処理するため、ビジネスロジックに集中して開発速度を向上させることができます。また、他のアリババサービスから様々なイベントソースを設定して、自動的にコードを実行するようにトリガーすることもできます。一番の利点は、コードが実際に消費するリソースを100ミリ秒単位で支払うだけでよいということです。

API Gatewayは、セキュアなAPIを簡単に大規模に公開できるマネージドサービスです。API Gatewayは、ECS、Function Compute、Webアプリケーションなど、多くのサービスとのインターフェースを提供しています。また、API GatewayはDNSサービスと統合して、ドメイン名ベースのAPIを提供します。

本日の記事では、Python 3を使用したサンプルプログラムを用いて、[Alibaba Cloud Function Compute](https://www.alibabacloud.com/product/function-compute?spm=a2c65.11461447.0.0.78f37ef6H54oV8) for DevOpsについて探っていきたいと思います。最終的な目標は、API Gateway、Function Compute、Direct Mailを組み合わせて、OSS上でホストされた静的なウェブサイトに適したコンタクトフォームを提供することです。

#Function Computeインタフェース
Function Computeでコードを実行するには、いくつかの方法があります。SDKを介して、API Gatewayを介して、あるいはTriggersを介して、関数を "invoke "することができます。関数の定義は、どのサービスがあなたの関数を呼び出すかによって異なります。この記事では、2つの異なる呼び出しのスタイルを取り上げます。1つ目はHTTPトリガーによる方法、2つ目はAPI Gatewayによる方法です。なお、関数定義は[Python](https://www.alibabacloud.com/blog/empower-your-web-with-python_594836?spm=a2c65.11461447.0.0.78f37ef6H54oV8)用のものになります。Pythonの関数については[こちら](https://www.alibabacloud.com/help/doc-detail/74756.htm?spm=a2c65.11461447.0.0.78f37ef6H54oV8)をご覧ください。

HTTPトリガーの関数定義では、HTTPトリガーとは何かを知る必要があります。HTTPトリガーとは、Function Compute コンソールで関数に割り当てるURLのことで、関数を呼び出すことができます。URLは次のようになります。

```
https://1234567891234567.ap-southeast-1.fc.aliyuncs.com/2016-08-15/proxy/service_name/function_name/
```

#HTTPトリガーで呼び出された場合の関数定義

```
def my_handler(environ, start_response):
    context = environ['fc.context']
    request_uri = environ['fc.request_uri']
    for k, v in environ.items():
        if k.startswith("HTTP_"):
            # process custom request headers
            pass

    # do something here

    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ["Hello World"]
```

|アイテム|説明と使用方法|
|:-----------|:----------------|
|関数名|my_handlerは、作成された関数の "Handler "フィールドに対応していなければなりません。例えば、関数の作成時にHandlerがmain.my_handlerに設定されている場合、Function Computeは自動的にmain.pyで定義されたmy_handler関数をロードします。|
|environ |environパラメータは、すべてのクライアント関連情報を保持する[Python](https://www.alibabacloud.com/blog/how-to-apply-api-gateway-for-directmail-in-python_594828?spm=a2c65.11461447.0.0.78f37ef6H54oV8)辞書です。詳細は [environ パラメータ](https://www.neoprime.xyz/info/alibaba/devops/fc_python_environ.php?spm=a2c65.11461447.0.0.78f37ef6H54oV8)のページを参照してください。|
|start_response| [start_response パラメータ](https://www.neoprime.xyz/info/alibaba/devops/fc_python_start_response.php?spm=a2c65.11461447.0.0.78f37ef6H54oV8)は callable です。詳細はstart_responseパラメータのページを参照してください。これはFCランタイムによって提供されます。必要な2つの位置パラメータとオプションのパラメータを受け付けます。|
|戻り値 |関数が戻る際には、呼び出し元のサーバに結果を返す必要があります。HTTPレスポンスには、HTTPステータス、HTTPヘッダ、HTTPボディが含まれている必要があります。そのため、関数が戻り値としてHTMLボディを返す前に、start_response()を呼び出してHTTPステータスとHTTPヘッダの内容をサーバに返す必要があります。この関数はHTMLボディを返します。|

#Invoke APIまたはAPI Gatewayから呼び出された場合の関数定義

```
def my_handler(event, context):
    body = 'hello world'
    res = {
        'isBase64Encoded': False,
        'statusCode': 200,
        'headers': {
            'content-type' : 'text/plain'
        },
        'body': body
    }

    return json.dumps(res)
```

|アイテム|説明と使用方法|
|:-----------|:----------------|
|関数名|my_handlerは、作成された関数の "Handler "フィールドに対応していなければなりません。例えば、関数作成時にHandlerがmain.my_handlerに設定されている場合、Function Computeは自動的にmain.pyで定義されたmy_handler関数をロードします。|
|イベントパラメータ| イベントパラメータは、関数呼び出しに渡されるデータです。Function Computeは変換を行わず、ユーザ関数に値を渡すだけです。パラメータの型はPython2.7ではstr、Python3ではbytesです。|
|コンテキストパラメータ| コンテキストパラメータには、関数の操作情報（リクエストIDや一時的なAccesskeyなど）が含まれています。パラメータはFCContext型です。Pythonガイドの項では、コンテキストの構造や使い方を説明しています。|
|戻り値| 関数の戻り値は、関数を呼び出した結果として返されます。どのような型であっても構いません。単純な型の場合、Function Computeは結果を返す前に文字列に変換します。複雑な型の場合、Function Computeは結果を返す前にJSON文字列に変換します。|

#HTMLでパラメータ "environ "を返すプログラム例
この例は、HTTPトリガー（HTTP URL）によって呼び出される関数の例です。

Function Compute にアップロードするための[サンプルプログラム](https://alicloud-common.oss-ap-southeast-1.aliyuncs.com/fc_environ.zip?spm=a2c65.11461447.0.0.78f37ef6H54oV8&file=fc_environ.zip)をダウンロードしてください。

[このプログラムが生成する出力を表示します。](https://www.neoprime.xyz/info/alibaba/devops/fc_function_1_output.php?spm=a2c65.11461447.0.0.78f37ef6H54oV8)データの一部はセキュリティ上の理由でマスクされていることに注意してください。

```
############################################################
# Version 1.01
# Date Created: 2018-05-25
# Last Update:  2018-05-29
# https://www.neoprime.io
# Copyright (c) 2018, NeoPrime, LLC
############################################################

""" Alibaba Cloud Function Compute Example """
""" This program will return the environment back to the caller as an HTML table """
""" This is useful to learn about the running environment in Function Compute """
""" This code is designed to be called thru HTTP URL via an HTTP Trigger """

def handler(environ, start_response):
    body = '<!DOCTYPE html>\n<html lang="en"><head>\n'
    body = body + '<style>\n'
    body = body + 'table, th, td {border: 1px solid black;}\n'
    body = body + 'table {border-collapse: collapse;}\n'
    body = body + 'th, td {border: 1px solid #ddd; padding 8px;}\n'
    body = body + 'th {padding-top: 12px; padding-bottom: 12px;\n'
    body = body + 'text-align: left; background-color: #4CAF50;color: white;}\n'
    body = body + 'tr:nth-child(even){background-color: #f2f2f2;}\n'
    body = body + 'tr:hover {background-color: #ddd;}\n'
    body = body + '</style>\n'
    body = body + '</head>\n<body><table width="%100%">\n'
    body = body + "<thead>\n<tr><th>Name</th><th>Type</th><th>Value</th></tr>\n</thead>\n"
    body = body + "<tbody>\n"

    for k, v in environ.items():
      body = body + "<tr>"
      body = body + "<td>" + k + "</td>"
      body = body + "<td>" + type(v).__name__ + "</td>"
      if isinstance(v, bool):
        body = body + "<td>" + str(v) + "</td>"
      if isinstance(v, str):
        body = body + "<td>" + v + "</td>"
      if isinstance(v, tuple):
        body = body + "<td>" + str(v) + "</td>"
      if isinstance(v, type):
        body = body + "<td>" + str(v) + "</td>"
      body = body + "</tr>\n"

    body = body + "</tbody>\n</table>\n</body></html>"
    status = '200 OK'
    response_headers = [('Content-type', 'text/html')]
    start_response(status, response_headers)
    return [bytes(body, "utf-8")]
```

#HTMLでパラメータ "context "を返すプログラム例
この例は、API Gatewayから呼び出される関数のためのものです。

[サンプルプログラム](https://alicloud-common.oss-ap-southeast-1.aliyuncs.com/fc_context.zip?spm=a2c65.11461447.0.0.78f37ef6H54oV8&file=fc_context.zip)をダウンロードして、Function Computeにアップロードします。

[このプログラムが生成する出力を表示します。](https://www.neoprime.xyz/info/alibaba/devops/fc_function_2_output.php?spm=a2c65.11461447.0.0.78f37ef6H54oV8)データの一部はセキュリティ上の理由でマスクされていることに注意してください。

```
############################################################
# Version 1.01
# Date Created: 2018-05-25
# Last Update:  2018-05-29
# https://www.neoprime.io
# Copyright (c) 2018, NeoPrime, LLC
############################################################

""" Alibaba Cloud Function Compute Example """
""" This program will return the environment back to the caller as an HTML table """
""" This is useful to learn about the running environment in Function Compute """
""" This code is designed to be called thru API Gateway using an HTTP URL """

import json

# Add a row to the HTML table
def add_row(body, name, value):
    """ body - the current HTML body string that we append to """
    """ name - the name of the item. Added to the first column """
    """ value - the value to add. The type is added to the second column, value to the third """
    """ returns body """

    # begin a new table row
    body = body + '<tr>\n'
    body = body + '<td>' + name + '</td>\n'
    body = body + '<td>' + type(value).__name__ + '</td>\n'

    if isinstance(value, str):
        # Keep the string length less than 85 characters
        v = (value[:85] + ' ...') if len(value) > 85 else value
        body = body + '<td>' + v + '</td>\n'
    else:
        body = body + '<td>' + str(value) + '</td>\n'

    return body + '</tr>\n'

# Add a "dict" item to the table by parsing thru each item in the dictionary
def add_dict(body, name, item):
    """ body - the current HTML body string that we append to """
    """ name - the name of the item. """
    """ item - The item to process """
    for k in item:
        if isinstance(item[k], dict):
            body = add_dict(body, name + "." + k, item[k])
        else:
            body = add_row(body, name + "." + k, item[k])
    return body

def add_event(body, event):
    j = json.loads(event.decode("utf-8"))
    return add_dict(body, "event", j)

def add_context(body, context):
    body = add_row(body, 'context.request_id', context.request_id)
    body = add_row(body, 'context.region', context.region)
    body = add_row(body, 'context.account_id', context.account_id)
    body = add_row(body, 'context.function.handler', context.function.handler)
    body = add_row(body, 'context.function.memory', context.function.memory)
    body = add_row(body, 'context.function.name', context.function.name)
    body = add_row(body, 'context.function.timeout', context.function.timeout)
    body = add_row(body, 'context.credentials.accessKeyId', context.credentials.accessKeyId)
    body = add_row(body, 'context.credentials.accessKeySecret', context.credentials.accessKeySecret)
    body = add_row(body, 'context.credentials.access_key_id', context.credentials.access_key_id)
    body = add_row(body, 'context.credentials.access_key_secret', context.credentials.access_key_secret)
    body = add_row(body, 'context.credentials.securityToken', context.credentials.securityToken)
    body = add_row(body, 'context.credentials.security_token', context.credentials.security_token)
    body = add_row(body, 'context.service.log_project', context.service.log_project)
    body = add_row(body, 'context.service.log_store', context.service.log_store)

    return body

def build_html(body, event, context):
    body = '<!DOCTYPE html>\n<html lang="en"><head>\n'
    body = body + '<style>\n'
    body = body + 'table, th, td {border: 1px solid black;}\n'
    body = body + 'table {border-collapse: collapse;}\n'
    body = body + 'th, td {border: 1px solid #ddd; padding 8px;}\n'
    body = body + 'th {padding-top: 12px; padding-bottom: 12px;\n'
    body = body + 'text-align: left; background-color: #4CAF50;color: white;}\n'
    body = body + 'tr:nth-child(even){background-color: #f2f2f2;}\n'
    body = body + 'tr:hover {background-color: #ddd;}\n'
    body = body + '</style>\n'
    body = body + '</head>\n<body><table width="100%">\n'
    body = body + '<thead>\n<tr><th>Name</th><th width="5%">Type</th><th>Value</th></tr>\n</thead>\n'
    body = body + '<tbody>\n'

    body = add_event(body, event)
    body = add_context(body, context)

    body = body + '</tbody>\n'
    body = body + '</table>\n</body></html>'

    return body

def handler(event, context):
    body = ""
    body = build_html(body, event, context)

    res = {
        'isBase64Encoded': False,
        'statusCode': 200,
        'headers': {
            'content-type' : 'text/html'
        },
        'body': body
    }

    return json.dumps(res)
```

サンプルプログラムでは、関数の環境や関数呼び出しで渡したパラメータを表示することができるようになります（HTTP経由かAPI Gatewayのどちらか）。

次回の記事では、署名付きのREST APIを使ってDirectMailのコードを作成します。次の記事では、HTMLコンタクトフォーム、API Gateway、Function Compute、DirectMailをサーバーレスシステムに統合して、ウェブサイトのコンタクトページを提供します。Webサイトは、OSS上の動的Webサイトでも静的Webサイトでもどちらでも構いません。

#Function Computeのアクセスキー
ここでは、Function Computeのアクセスキーを安全に管理する方法について説明します。RAMロールです。ロールを使用すると、Alibaba Consoleでロールを介してアクセスキーを作成し、そのロールをFunction Computeサービスに割り当てることができます。これにより、ソースコードでキーが必要になることを防ぐことができます。ロールの資格情報は、context.credentialsパラメータを介して関数で利用できるようになります。

Resource Access Managementコンソールで、必要なパーミッションを持つロールを作成します。そして、このロールをFunction Computeサービスに割り当てます。このサービスの下に位置する各関数には、このロールが割り当てられています。

#アリババドキュメンテーション
Function Compute：
[アリババ Function Computeの製品ページ](https://www.alibabacloud.com/product/function-compute?spm=a2c65.11461447.0.0.78f37ef6H54oV8)
[アリババ Function Compute ドキュメント](https://www.alibabacloud.com/help/product/50980.htm?spm=a2c65.11461447.0.0.78f37ef6H54oV8)
[アリババ Function Compute トリガー](https://www.alibabacloud.com/help/doc-detail/53102.htm?spm=a2c65.11461447.0.0.78f37ef6H54oV8)

API Gateway：
[アリババAPIゲートウェイ製品ページ](https://www.alibabacloud.com/product/api-gateway?spm=a2c65.11461447.0.0.78f37ef6H54oV8)
[アリババAPIゲートウェイのドキュメント](https://www.alibabacloud.com/help/product/29462.htm?spm=a2c65.11461447.0.0.78f37ef6H54oV8)
[API Gateway SSL証明書のインストール](https://www.alibabacloud.com/help/doc-detail/48866.htm?spm=a2c65.11461447.0.0.78f37ef6H54oV8)

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*