tag:crieit.net,2005:https://crieit.net/tags/lambda/feed
「lambda」の記事 - Crieit
Crieitでタグ「lambda」に投稿された最近の記事
2021-05-13T23:16:42+09:00
https://crieit.net/tags/lambda/feed
tag:crieit.net,2005:PublicArticle/17118
2021-05-13T23:16:42+09:00
2021-05-13T23:16:42+09:00
https://crieit.net/posts/Lambda-DeadLetterQueue-DLQ
Lambda関数でDeadLetterQueue(DLQ)を試す
<p>AWSのLambda関数をイベントで起動した時、複数回エラーを起こしたら、DeadLetterQueue、すなわちDLQを飛ばしたい。DLQを試すもの。Cloudformation使う。DLQはSNSとSQSが指定できるが、今回はSNSを使う。</p>
<h2 id="系"><a href="#%E7%B3%BB">系</a></h2>
<p>以下の系を試す。</p>
<p>CloudWatch -> Lambda -(error)-> SNS -> Email</p>
<h2 id="やってみる"><a href="#%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">やってみる</a></h2>
<p>CloudWatchで1分ごとにLambda関数を起動するようなものを、Cloudformationwと使って用意する。SNS Topicについては、コンソールから作成する。</p>
<h3 id="SNS Topicを作る"><a href="#SNS+Topic%E3%82%92%E4%BD%9C%E3%82%8B">SNS Topicを作る</a></h3>
<p>コンソールからSNS Topicを作成する。名前は仮に DeadLetterQueueTopicとする。とりあえずEメールに飛ばすようにしておくと、確認しやすいと思う。</p>
<h3 id="ディレクトリ構成"><a href="#%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E6%A7%8B%E6%88%90">ディレクトリ構成</a></h3>
<p>ディレクトリ構成は以下。__init__.pyは空。</p>
<p>.<br />
├── __init__.py<br />
├── hello_world_function<br />
│ ├── __init__.py<br />
│ ├── hello_world<br />
│ │ ├── __init__.py<br />
│ │ └── app.py<br />
│ └── requirements.txt<br />
├── samconfig.toml<br />
└── template.yaml</p>
<p>という構成でやってみる。</p>
<h3 id="template.yaml"><a href="#template.yaml">template.yaml</a></h3>
<p>TargetArn に SNS Topic のARNを入れる。</p>
<pre><code>AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world_function
Handler: hello_world/app.lambda_handler
Runtime: python3.8
Policies:
- AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/AmazonSNSFullAccess
Events:
HelloWorld:
Type: Schedule
Properties:
Schedule: rate(1 minute)
DeadLetterQueue:
Type: SNS
TargetArn: arn:aws:sns:xxxxxxxxxxxxxxxxxx:DeadLetterQueueTopic
</code></pre>
<h3 id="samconfig.toml"><a href="#samconfig.toml">samconfig.toml</a></h3>
<p>sam build –use-container の後に sam deploy –guided で作るとよい。以下のようになる。</p>
<pre><code>version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "sam-app"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-1miie23rwqpqg"
s3_prefix = "sam-app"
region = "ap-northeast-1"
capabilities = "CAPABILITY_IAM"
</code></pre>
<h3 id="requirement.txt"><a href="#requirement.txt">requirement.txt</a></h3>
<p>空でもいいけど。</p>
<pre><code>requests
six
regex
</code></pre>
<h3 id="hello_world/app.py"><a href="#hello%26%2395%3Bworld%2Fapp.py">hello_world/app.py</a></h3>
<pre><code>def lambda_handler(event, context):
return True
</code></pre>
<h3 id="DLQ"><a href="#DLQ">DLQ</a></h3>
<pre><code>sam build --use-container
sam deploy
</code></pre>
<p>デプロイすると、1分間にLambda関数が起動する。で、このままだと正常に終了してDLQが飛ばないので、わざとエラーを起こす。そのためには、上記コードの raise Exception のエラーを外してデプロイする。しばらく待っていたら、DLQがSNSに飛ぶ。SNSのSubscriberにEmailを用意しておけば、すぐに確認できる。</p>
<h3 id="後片付け"><a href="#%E5%BE%8C%E7%89%87%E4%BB%98%E3%81%91">後片付け</a></h3>
<p>SNSのトピックは手動で作成したので、手動で消す。</p>
<p>忘れずにスタックを消す。消し忘れると延々とLambda関数が起動し続けることになってしまうので注意。<br />
<code>aws cloudformation delete-stack --stack-name sam-app</code></p>
<h2 id="DLQ -> SNS -> Lambdaにした時のproperty"><a href="#DLQ+-%26gt%3B+SNS+-%26gt%3B+Lambda%E3%81%AB%E3%81%97%E3%81%9F%E6%99%82%E3%81%AEproperty">DLQ -> SNS -> Lambdaにした時のproperty</a></h2>
<p>Slackとかに飛ばしたい時は、DLQのSNS経由でLambda関数に飛ばすことになる。Lambda関数のeventのpropertyをメモしておく。</p>
<pre><code>{
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "xxxxx",
"Sns": {
"Type": "Notification",
"MessageId": "xxxx",
"TopicArn": "xxxx"
"Subject": None,
"Message": "xxxxxxx",
"Timestamp": "2021-05-13T03:02:52.129Z",
"SignatureVersion": "1",
"Signature": "xxxx",
"SigningCertUrl": "xxxx",
"UnsubscribeUrl": "xxxx",
"MessageAttributes": {
"RequestID": {
"Type": "String",
"Value": "xxxx",
},
"ErrorCode": {
"Type": "String",
"Value": "200"
}
}
}
}
]
}
</code></pre>
<p>気になるのは event.Sns.Message だが、これはシリアライズされたJSON。json.loads()でデシリアライズすると以下のようになる。</p>
<pre><code>{
"version": "0",
"id": "xxxx",
"detail-type": "Scheduled Event",
"source": "aws.events",
"account": "xxxx",
"time": "2021-05-13T02:47:42Z",
"region": "xxxx",
"resources": [
"xxxx",
],
"detail": {}
}
</code></pre>
<p>resourcesとidから、エラーを起こした元のLambda関数を辿ることができる。</p>
<h2 id="参考URL"><a href="#%E5%8F%82%E8%80%83URL">参考URL</a></h2>
<p>ありがとうございました。</p>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-scheduledevents-example-use-app-spec.html">CloudWatch イベント アプリケーションの AWS SAM テンプレート – AWS Lambda</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-property-function-deadletterqueue.html">DeadLetterQueue – AWS Serverless Application Model</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-policies">AWS::Serverless::Function – AWS Serverless Application Model</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/articles/eventbridge-supports-dead-letter-queue/">[アップデート]EventBridgeのデッドレターキュー(DLQ)を使ってみた | DevelopersIO</a></li>
</ul>
<p>関連コンテンツ</p>
<h2 id="関連記事"><a href="#%E9%96%A2%E9%80%A3%E8%A8%98%E4%BA%8B">関連記事</a></h2>
<p>スポンサーリンク</p>
tama
tag:crieit.net,2005:PublicArticle/16650
2021-01-26T03:25:25+09:00
2021-01-26T03:43:15+09:00
https://crieit.net/posts/AWS-Lambda
AWS Lambdaのロギングを考える
<p>ロギングをどうするかで困っていた。AWS Lambdaでは、プリント出力したものがCloudWatchに保存されてとても便利なのだが、考えもなしにとりあえずポンポン入れていたところ、確かに情報はあるので追えないことはないんだが、地道に時間にあたりをつけて検索するなど、非常に泥臭い作業が要求され、なにかとつらかった。</p>
<p>APIのコール回数など集計したいという要件も出てきて、重い腰をあげてロギングについて頑張って考えました、という話。</p>
<h2 id="ユースケースを考える"><a href="#%E3%83%A6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%92%E8%80%83%E3%81%88%E3%82%8B">ユースケースを考える</a></h2>
<p>AWS Lambdaで、何も考えずにとにかく必要そうなのをログ出力しているが、フォーマットも何も整っていないため、ほしい情報を探し当てるのも一苦労だ。</p>
<p>ログをjson形式に構造化させてやると、CloudWatch Insightsでクエリを投げて検索・集計することができる。ということで、ユースケースに対応できるように、ログ出力について考えたい。</p>
<p>ユースケースとして以下を考慮。</p>
<ul>
<li>ERRORだけ抜き出す</li>
<li>ユーザー別に叩いたAPIについて集計する</li>
<li>詳細な検索ができるように、ハンドラに渡された情報は全部取っておく</li>
</ul>
<p>前提条件として、node.jsを使っていたりPythonを使っていたりする。そのため、特定の言語のライブラリに依存した解決はあまり好ましくない。</p>
<h2 id="結果"><a href="#%E7%B5%90%E6%9E%9C">結果</a></h2>
<p>以上の条件を考えて色々調査し、必要なことを考えた結果、以下のような感じになった。</p>
<pre><code>{
"msg": string // フィルタリングできるような固有のメッセージ
"funcName": string // 実行関数
"dateTime": string // ログ出力した時刻 RFC3339 に則る(jsならDateオブジェクトをconsole出力すればよい)
"level": INFO | WARN | ERROR,
"event": object // Lambda関数のeventパラメータをそのまま
"input": object // 必要なやつ
}
</code></pre>
<p>levelでログレベルを設定。inputとmsgでフィルタリング、集計を容易に。困ったらeventとfuncNameで頑張って追えるようにしておいた。また、独自フォーマットなので言語やライブラリに依存しない。</p>
<p>このフォーマットはほとんど「<a target="_blank" rel="nofollow noopener" href="https://www.scalyr.com/blog/aws-lambda-logging-best-practices/">AWS Lambda Logging: An Intro How-To and Best Practices | Scalyr</a>」を参考にしています。</p>
<p>以下のような使い方を想定(node.js)。</p>
<pre><code>function logging(msg, funcName, event, level, inputValues) {
const logMsg = {
msg: msg,
funcName: funcName,
dateTime: new Date(),
level: level,
event: event,
input: inputValues
}
if (level === "INFO") {
console.info(JSON.stringify(logMsg))
} else if (level === "WARN") {
console.warn(JSON.stringify(logMsg))
} else if (level === "ERROR") {
console.error(JSON.stringify(logMsg))
} else {
console.log(JSON.stringify(logMsg))
}
}
exports.handler = async (event, context, callback) => {
logging("CallApi", "functions/users/app.handler", event, "INFO", {userId: "testUser"});
}
</code></pre>
<p>で、CloudWatch Insightsを使うとたとえば以下のようなクエリを投げて集計できるわけだ。</p>
<pre><code>filter msg like /CallApi/
| stats count(input.userId) by input.userId
</code></pre>
<p>運用を通じてブラッシュアップしていきたい。</p>
<h2 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h2>
<p>この記事は、開発中のWebサービスQnQで草稿を作りました。よかったら見ていってね(宣伝)。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://qnqtree.com/tree/dB3cNjWRbkAeTZsvcpJs">Lambda関数のロギング設計を考える | QnQ</a></p>
<p>その他、参考にさせていただいた記事です。ありがとうございました。</p>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://www.scalyr.com/blog/aws-lambda-logging-best-practices/">AWS Lambda Logging: An Intro How-To and Best Practices | Scalyr</a>
<ul>
<li>一番参考にした記事</li>
</ul></li>
<li><a target="_blank" rel="nofollow noopener" href="https://recipe.kc-cloud.jp/archives/9968">Lambdaの本番業務利用を考える① – ログ出力とエラーハンドリング – ナレコムAWSレシピ</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-logging.html">Node.js の AWS Lambda 関数ログ作成 - AWS Lambda</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tonluqclml/items/780370a4575781eb19df">AWS Lambda/PythonでJSON形式でログを出すベストプラクティス - Qiita</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://scrapbox.io/tasuwo/Node.js_%E3%81%AE_Lambda_%E3%83%AD%E3%82%B0%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">Node.js の Lambda ログのフォーマットについて - tasuwo's notes</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://medium.com/@jyotti/node-bunyan%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Faws-lambda%E3%81%A7%E3%81%AE%E3%81%AE%E3%83%AD%E3%82%AE%E3%83%B3%E3%82%B0%E5%87%A6%E7%90%86-cd45faa2b261">node-bunyanを使ったAWS Lambdaでのロギング処理 | by Atsushi Nakajo | Medium</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://dev.classmethod.jp/articles/how-to-cloudwatch-logs-insights/">CloudWatch Logs Insightsでログを調査する前に読む記事 | Developers.IO</a></li>
</ul>
tama
tag:crieit.net,2005:PublicArticle/15858
2020-04-21T13:51:26+09:00
2020-04-21T14:04:20+09:00
https://crieit.net/posts/AWS-Lambda-cx-Oracle-OracleDB
AWS Lambdaでcx_Oracleを使ってOracleDBへ接続する
<h1 id="前書き"><a href="#%E5%89%8D%E6%9B%B8%E3%81%8D">前書き</a></h1>
<p>Pythonで書かれた Lambdaファンクションから、OracleDBへ接続する方法を記載します。</p>
<p>LambdaファンクションからMySQLへの接続はたくさん記事があるんだけど、OracleDBへの接続はあまり記事がなく、実現まで結構時間がかかったので、自分用の備忘録に。</p>
<h1 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h1>
<ul>
<li>LambdaファンクションからOracleDBに接続する時には、cx_Oracleのモジュールだけではなく、Oracle Instant Clientのモジュールも、デプロイパッケージに入れてあげる必要がある。</li>
<li>すべてのモジュールを一つのデプロイパッケージに入れると、大きすぎてAWS Lambdaのインラインエディタで編集できなくなる。なので、cx_OracleとOracle Instant Clientのモジュールはレイヤに分けると良い。</li>
</ul>
<h1 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h1>
<h2 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h2>
<p>2020年4月時点で、使用した環境は下記の通りです。<br />
python3のインストールまで完了している状態で、後続の作業を開始します。</p>
<ul>
<li>Amazon Linux 2(AWS EC2上で作業)</li>
<li>python3.7</li>
<li>cx_Oracle7.3</li>
<li>Oracle Instant Client 19.6</li>
</ul>
<h2 id="手順の流れ"><a href="#%E6%89%8B%E9%A0%86%E3%81%AE%E6%B5%81%E3%82%8C">手順の流れ</a></h2>
<ol>
<li>必要なモジュールをインストールする</li>
<li>デプロイパッケージを作る</li>
<li>Lambdaファンクションを作成する</li>
<li>レイヤ用のデプロイパッケージを作成する</li>
<li>Lambdaファンクションにレイヤを適用する</li>
</ol>
<h2 id="1. 必要なモジュールをインストールする"><a href="#1.+%E5%BF%85%E8%A6%81%E3%81%AA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B">1. 必要なモジュールをインストールする</a></h2>
<h3 id="Oracle Instant Client"><a href="#Oracle+Instant+Client">Oracle Instant Client</a></h3>
<p>まずは、Oracle Instant Client から。<br />
最新版のOracle Instant Clientを公式サイトからダウンロードします。作業環境が Amazon Linux 2 なので、Linux用の64ビット版を使います。<br />
* <a target="_blank" rel="nofollow noopener" href="https://www.oracle.com/technetwork/jp/database/database-technologies/instant-client/overview/index.html">Oracle Instant Client</a><br />
* <a target="_blank" rel="nofollow noopener" href="https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html">Instant Client Downloads for Linux x86-64 (64-bit)</a></p>
<p>インストールしたのは、とりあえずbasicとsqlplusのふたつだけ。LambdaファンクションからのDB接続だけを考えるなら、sqlplusは不要かも。</p>
<pre><code>$ sudo yum -y localinstall ./oracle-instantclient19.6-basic-19.6.0.0.0-1.x86_64.rpm
$ sudo yum -y localinstall ./oracle-instantclient19.6-sqlplus-19.6.0.0.0-1.x86_64.rpm
</code></pre>
<p>インストール完了後、SQL*PLUSでDBに接続できることを確認しておきます。<br />
なお、Oracle Instant Clientを使用する分には、ORACLE_HOMEなどの設定は不要らしいです。</p>
<pre><code>$ sqlplus <user>/<pass>@<hostname>:<port>/<sid>
SQL*Plus: Release 19.0.0.0.0 - Production on xxx xxx xx xx:xx:xx xxxx
Version 19.6.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
Last Successful login time: xxx xxx xx xxxx xx:xx:xx +09:00
Connected to:
Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production
SQL> select TNAME from tab;
TNAME
--------------------------------------------------------------------------------
SAMPLE_EMP
SQL> select * from SAMPLE_EMP;
EMPNO EMPNAME GENDER_F
---------- -------------------------------------------------- ----------
0000000001 EMP_A 1
0000000002 EMP_B 2
0000000003 EMP_C 1
SQL> quit
Disconnected from Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production
</code></pre>
<p>サンプルで作っておいたテーブルの中身が見えており、Oracle Instant Clientのインストールは完了です。</p>
<h3 id="cx_Oracle"><a href="#cx_Oracle">cx_Oracle</a></h3>
<p>PythonでOracleDBに接続するために、cx_Oracleもインストールしておきます。</p>
<pre><code>$ sudo pip3 install cx_oracle
</code></pre>
<h3 id="EC2からcx_Oracleを使ってOracleDBへ接続する"><a href="#EC2%E3%81%8B%E3%82%89cx_Oracle%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6OracleDB%E3%81%B8%E6%8E%A5%E7%B6%9A%E3%81%99%E3%82%8B">EC2からcx_Oracleを使ってOracleDBへ接続する</a></h3>
<p>ここまで完了したら、pythonでOracleDBへ接続できるようになっているはずなので、簡単なサンプルでOracleDBに接続できるか確認します。例えば下記のような感じ。ファイル名は<code>dbconnect_oracle.py</code>とします。</p>
<pre><code>import json
import cx_Oracle
def lambda_handler(event, context):
# TODO implement
# cx_oracleバージョン確認
verOraCli = cx_Oracle.clientversion()
print( verOraCli )
# oracle接続
print( "DB connect" )
connection = cx_Oracle.connect("<user>", "<pass>", "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=<hostname>) (PORT=<port>))(CONNECT_DATA=(SID=<sid>)))")
# select実行
print( "select execute" )
try:
cursor = connection.cursor()
sql = "select * from sample_emp"
cursor.execute( sql )
result = cursor.fetchall()
print( result )
finally:
cursor.close()
connection.close()
return {
'statusCode': 200,
'body': json.dumps( 'cx_oracle clientversion = ' + str(verOraCli) )
}
</code></pre>
<p>本来はハンドラの中でDB接続処理を行わない方が良いけど、まあとりあえずということでひとつ。早速実行してみます。</p>
<pre><code>$ python3 -c "import dbconnect_oracle; dbconnect_oracle.lambda_handler(0,0)"
(19, 6, 0, 0, 0)
DB connect
select execute
[('0000000001', 'EMP_A', 1), ('0000000002', 'EMP_B', 2), ('0000000003', 'EMP_C', 1)]
</code></pre>
<p>Oracle Instant Client 19.6 を使用してOracleDBでselectを実行できていることが確認できました。</p>
<h2 id="2. デプロイパッケージを作る"><a href="#2.+%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E3%82%8B">2. デプロイパッケージを作る</a></h2>
<p>EC2上での動作は確認できたので、AWS Lambdaの関数として定義するためにデプロイパッケージを作成します。</p>
<h3 id="デプロイパッケージ構成"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E6%A7%8B%E6%88%90">デプロイパッケージ構成</a></h3>
<p>Lambdaファンクションで外部ライブラリのimportを行う場合、外部ライブラリを含んだデプロイパッケージを作る必要があります。今回はさらに、cx_OracleがOracle Instant Clientを利用するので、Oracle Instant Clinentもデプロイパッケージに含める必要があります。</p>
<p>というわけで、目指すべき構成は下記の形。作業ディレクトリは<code>/tmp/dbconnect_oracle_pack</code>とします。</p>
<pre><code>/tmp/dbconnect_oracle_pack
├ dbconnect_oracle.py
├ cx_Oracle.cpython-37m-x86_64-linux-gnu.so
├ cx_Oracle-7.3.0.dist-info
│ ├ INSTALLER
│ ├ METADATA
│ ├ RECORD
│ ├ WHEEL
│ └ top_level.txt
└ lib
├ libaio.so -> ./libaio.so.1
├ libaio.so.1
├ libclntsh.so -> ./libclntsh.so.19.1
├ libclntsh.so.19.1
├ libclntshcore.so -> ./libclntshcore.so.19.1
├ libclntshcore.so.19.1
├ libipc1.so
├ libmql1.so
├ libnnz19.so
├ libocci.so -> ./libocci.so.19.1
├ libocci.so.19.1
└ libociei.so
</code></pre>
<h3 id="デプロイパッケージ作成"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90">デプロイパッケージ作成</a></h3>
<p>デプロイパッケージにcx_Oracleを含めるため、作業ディレクトリへcx_Oracleを持ってきます。作業ディレクトリへのインストールなので、今回は<code>sudo</code>は不要です。</p>
<pre><code>$ pip3 install cx_Oracle -t /tmp/dbconnect_oracle_pack
</code></pre>
<p>Oracle Instant Clientのモジュールは、インストール先からコピーします。<code>libaio.so.1</code>だけは、<code>/usr/lib64/</code>から持ってきます。</p>
<p>なお、記事を書く際に再度確認したら、<code>libaio.so.1</code>はシンボリックリンクで、実体は<code>libaio.so.1.0.1</code>でした。<br />
下記の通りでも動作はしますが、気になる方は、<code>libaio.so.1.0.1</code>をコピーしてそこからシンボリックリンクを作成する方が良いかもしれません。</p>
<pre><code>$ mkdir /tmp/dbconnect_oracle_pack
$ cp /usr/lib/oracle/19.6/client64/lib/libclntsh.so.19.1 /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libclntshcore.so.19.1 /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libipc1.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libmql1.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libnnz19.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libocci.so.19.1 /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libociei.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib64/libaio.so.1 /tmp/dbconnect_oracle_pack/lib
</code></pre>
<p>最初に挙げたデプロイパッケージ構成に不足している部分を、シンボリックリンクで作成します。<br />
デプロイパッケージのサイズ上限は250MBのため、絞れるところは絞りましょう。</p>
<pre><code>$ cd /tmp/dbconnect_oracle_pack
$ ln -s ./libaio.so.1 libaio.so
$ ln -s ./libclntsh.so.19.1 libclntsh.so
$ ln -s ./libclntshcore.so.19.1 libclntshcore.so
$ ln -s ./libocci.so.19.1 libocci.so
</code></pre>
<p>ここまで完了すると、デプロイパッケージ構成通りの状態となっているはずです。zip圧縮して、デプロイパッケージを作成します。圧縮後のファイルは70MB弱くらいになると思います。<br />
シンボリックリンクを含んでいるので、<code>--symlinks</code>オプションを忘れないように。また、圧縮対象は<code>/tmp/dbconnect_oracle_pack</code>ディレクトリではなく、その配下であることに注意してください。</p>
<pre><code>$ cd /tmp/dbconnect_oracle_pack
$ zip -r --symlinks dbconnect_oracle_sample.zip ./dbconnect_oracle.py ./cx_Oracle.cpython-37m-x86_64-linux-gnu.so ./cx_Oracle-7.3.0.dist-info ./lib
</code></pre>
<h2 id="3. Lambdaファンクションを作成する"><a href="#3.+Lambda%E3%83%95%E3%82%A1%E3%83%B3%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">3. Lambdaファンクションを作成する</a></h2>
<p>完成したデプロイパッケージ元にLambdaファンクションを作成します。</p>
<p>ですが、作成したデプロイパッケージが50MBを超えているため、Lambdaファンクションとして直接アップロードを行う事ができません。一旦S3上にアップロードした後、LambdaファンクションにS3からアップロードする形になります。なお、Lambdaファンクションのサイズ上限は諸々含めて250MB。cx_OracleとOracle Instant Client だけで200MBを超えていて、結構カツカツです。</p>
<p>Lambdaファンクションとしてのアップロードが完了すると、インラインエディタで確認ができる…のが通常なのですが、サイズが大きすぎてインラインエディタでは編集できない旨の警告が表示されます。実行はできるようなので、良しとしておきます。</p>
<p><a href="https://crieit.now.sh/upload_images/97382e5555931319dd5fd600612effab5e9e5b7d04327.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/97382e5555931319dd5fd600612effab5e9e5b7d04327.png?mw=700" alt="デプロイパッケージが大きすぎてインラインコード編集を有効にできません" /></a></p>
<p>OracleDBに限らず、DBに接続するLambdaファンクションにはIPアドレスの付与が必要です。VPC設定を行い、VPC設定、サブネット、セキュリティグループを指定します。</p>
<p>また、初回実行に限り、そこそこの時間がかかります。デフォルトのタイムアウト値は3秒となっていますが、10秒程度に増やしておいたほうがよいでしょう。</p>
<p>上記の設定を完了したら、テスト実行を行います。サンプルは引数不要のイベントハンドラなので、空のテストイベントを作成してテストを行いましょう。</p>
<p><a href="https://crieit.now.sh/upload_images/1874ff95cb0fe728e14c95e0dc913dae5e9e5d40e3989.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1874ff95cb0fe728e14c95e0dc913dae5e9e5d40e3989.png?mw=700" alt="実行結果:成功" /></a></p>
<p>ヨシ! ちゃんと、Oracle Instant Client 19.6が使われているようです。<br />
初回は時間がかかりましたが、2回目からは1秒かからずに実行されます。時間がかかるのは、IP付与によるものかしら。</p>
<h2 id="4. レイヤ用のデプロイパッケージを作成する"><a href="#4.+%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">4. レイヤ用のデプロイパッケージを作成する</a></h2>
<p>当初の目的である、cx_Oracleを使ってOracleDBへ接続するLambdaファンクションの作成は、ここまでで実現できました。</p>
<p>ただ、インラインエディタでの編集ができないのはちょっといただけません。サイズが大きい原因はcx_OracleとOracle Instant Clientなので、この部分をcx_Oracleレイヤとして切り出せば良いのではないか?</p>
<h3 id="cx_Oracleレイヤ用のデプロイパッケージ構成"><a href="#cx_Oracle%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E6%A7%8B%E6%88%90">cx_Oracleレイヤ用のデプロイパッケージ構成</a></h3>
<p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html">AWS Lambda レイヤー</a></p>
<blockquote>
<p>関数コードからランタイム依存関係を移動するには、それらをレイヤーに配置します。Lambda ランタイムには、関数コードを使用して、レイヤーに含まれるライブラリにアクセスできることを確認する、<code>/opt</code>ディレクトリのパスが含まれます。</p>
<p>ライブラリをレイヤーに含めるには、ランタイムでサポートされているいずれかのフォルダにそれらを配置します。<br />
* <strong>Python</strong> – <code>python</code>、<code>python/lib/python3.8/site-packages</code>(サイトディレクトリ)</p>
</blockquote>
<p>レイヤ用のデプロイパッケージを作る場合、pyhon関連のライブラリは<code>python</code>配下に、配置します。なので、cx_Oracle関連のモジュールはこちらに置きます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-envvars.html">AWS Lambda 環境変数の使用</a></p>
<blockquote>
<p>予約されていない環境変数<br />
(略)<br />
<code>LD_LIBRARY_PATH</code> – システムライブラリのパス<br />
(<code>/lib64:/usr/lib64:$LAMBDA_RUNTIME_DIR:$LAMBDA_RUNTIME_DIR/lib:$LAMBDA_TASK_ROOT:$LAMBDA_TASK_ROOT/lib:/opt/lib</code>)。</p>
</blockquote>
<p>Oracle Instant Clientのモジュールについては、<code>LD_LIBRARY_PATH</code>で指定されたパスに配置する必要があります。Lambdaファンクションの設定で編集しても良いのですが、デフォルトで指定されたパス<code>/opt/lib</code>があるので、そちらを使いましょう。レイヤに含まれるライブラリは<code>/opt</code>配下に配置されるとのことなので、<code>lib</code>配下です。</p>
<p>というわけで、レイヤ用デプロイパッケージの構成は下記の通りになります。作業ディレクトリは<code>/tmp/dbconnect_oracle_pack/layer</code>とします。</p>
<pre><code>/tmp/dbconnect_oracle_pack/layer
├ python
│ ├ cx_Oracle.cpython-37m-x86_64-linux-gnu.so
│ └ cx_Oracle-7.3.0.dist-info
│ ├ INSTALLER
│ ├ METADATA
│ ├ RECORD
│ ├ WHEEL
│ └ top_level.txt
└ lib
├ libaio.so -> ./libaio.so.1
├ libaio.so.1
├ libclntsh.so -> ./libclntsh.so.19.1
├ libclntsh.so.19.1
├ libclntshcore.so -> ./libclntshcore.so.19.1
├ libclntshcore.so.19.1
├ libipc1.so
├ libmql1.so
├ libnnz19.so
├ libocci.so -> ./libocci.so.19.1
├ libocci.so.19.1
└ libociei.so
</code></pre>
<h3 id="レイヤ用デプロイパッケージ作成"><a href="#%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90">レイヤ用デプロイパッケージ作成</a></h3>
<p>配置ができたらzip圧縮し、レイヤ用デプロイパッケージを作成します。ファイル名は何でも良いですが、<code>oraclelib.zip</code>としてみました。</p>
<pre><code>$ cd /tmp/dbconnect_oracle_pack/layer
$ zip -r --symlinks oraclelib.zip ./python ./lib
</code></pre>
<h2 id="5. Lambdaファンクションにレイヤを適用する"><a href="#5.+Lambda%E3%83%95%E3%82%A1%E3%83%B3%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E3%83%AC%E3%82%A4%E3%83%A4%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B">5. Lambdaファンクションにレイヤを適用する</a></h2>
<p>レイヤを作るため、AWS Lambdaの左側メニューから「レイヤー」を選択し、レイヤの作成を行います。</p>
<p>先程と同様に、S3経由でアップロードする形になります。また、「互換性のあるランタイム」には、作業環境であるAmazon Linux 2にインストールしたのと同じ Python3.7 を指定します。</p>
<p><a href="https://crieit.now.sh/upload_images/731c2c355d8a949923189d7ba3ba8e7b5e9e7566a330b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/731c2c355d8a949923189d7ba3ba8e7b5e9e7566a330b.png?mw=700" alt="レイヤー作成設定" /></a></p>
<p>レイヤの作成が完了したら、Lambdaファンクションにレイヤを適用します。</p>
<p>作成したLambdaファンクションの画面から「レイヤーの追加」を行う事ができます。<br />
Lambdaファンクションのランタイムを「Python3.7」にした上で、「レイヤーの追加」を行ってください。</p>
<p>選択できるレイヤーは、Lambdaファンクションで設定しているランタイムを「互換性のあるランタイム」で指定したレイヤーだけです。<br />
<a href="https://crieit.now.sh/upload_images/505ac0668a3516271d2a73ce9c2b569a5e9e765c9c085.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/505ac0668a3516271d2a73ce9c2b569a5e9e765c9c085.png?mw=700" alt="関数にレイヤーを追加" /></a></p>
<p>こうしておくと、Lambdaファンクションそのものは、今回のサンプルの<code>dbconnect_oracle.py</code>だけにしても大丈夫です。インラインコード編集も可能となります。</p>
<p><a href="https://crieit.now.sh/upload_images/7d20015f8e9283b81e552707518b66645e9e77bc2ef5e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7d20015f8e9283b81e552707518b66645e9e77bc2ef5e.png?mw=700" alt="OK" /></a></p>
<p>動作も問題ありません。</p>
<h1 id="後書き"><a href="#%E5%BE%8C%E6%9B%B8%E3%81%8D">後書き</a></h1>
<p>一番わからなかったのは、Oracle Instant Client 19 だとどのlibをデプロイパッケージに含めればよいのか、ということです。今回記事に残そうとしたのも、それが要因です。</p>
<p>それにしても、Oracle Instant Client だけで200MBオーバーなんだけど、そのうちOracle Instant ClientだけでLambdaのサイズ制限を超えてしまうのではないだろうか。</p>
<h1 id="参考サイト"><a href="#%E5%8F%82%E8%80%83%E3%82%B5%E3%82%A4%E3%83%88">参考サイト</a></h1>
<ul>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/aidy91614/items/92987d547c318e0483f5">Lambda から RDS にアクセスする方法 (python)</a></li>
<li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ikeisuke/items/45ba4d88b887d793c444">AWS Lambda (Node.js-v4.3.2)からOracleに接続する(ORA-21561への対応)</a></li>
<li><p><a target="_blank" rel="nofollow noopener" href="http://wiki.nikhil.io/Lambda,_Python,_and_Oracle/raw">Lambda, Python, and Oracle</a></p>
<p>その他、検索に引っかかったフォーラムの記事とか色々参考にしました。</p></li>
</ul>
平
tag:crieit.net,2005:PublicArticle/14700
2018-12-28T19:11:14+09:00
2018-12-28T19:11:14+09:00
https://crieit.net/posts/Puppeteer-on-AWS-Lambda
Puppeteer on AWS Lambda のスクリーンショットで文字が豆腐になるので、ウェブフォントと絵文字フォントをインストール
<h2 id="状況"><a href="#%E7%8A%B6%E6%B3%81">状況</a></h2>
<p>現在開発中の https://poiit.me で puppeteer のスクリーンショットを使って動的OGPをつくっています。<br />
CSSで動的な画像をデザインできるし、一つ lambda 関数を作っておけば、いろんなところで応用ができるのでいいですね。</p>
<p>が、今回は絵文字が、豆腐になってしまったのでその対処法。</p>
<p><img src="https://qiita-image-store.s3.amazonaws.com/0/6531/6a6ea738-cf5e-15cb-4eb3-acb2dc85e5d8.jpeg" alt="yahsan2-ogp.jpg" /></p>
<h2 id="参考記事"><a href="#%E5%8F%82%E8%80%83%E8%A8%98%E4%BA%8B">参考記事</a></h2>
<p>ベースはこちらの記事を参考にしました。<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/zyyx-matsushita/items/c33f79e33f242395019e">https://qiita.com/zyyx-matsushita/items/c33f79e33f242395019e</a></p>
<p>基本は参考Qiita をベースに対応できましたが、絵文字フォントがどこにあるのか結構探したので、備忘録的にメモ。</p>
<h2 id="絵文字フォントダウンロードと設置"><a href="#%E7%B5%B5%E6%96%87%E5%AD%97%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89%E3%81%A8%E8%A8%AD%E7%BD%AE">絵文字フォントダウンロードと設置</a></h2>
<p>Google の Noto Color Emoji はこちらから ダウンロードできました。<br />
<a target="_blank" rel="nofollow noopener" href="https://www.google.com/get/noto/#noto-emoji-zsye-color">https://www.google.com/get/noto/#noto-emoji-zsye-color</a></p>
<p><strong>他のダウンロード可能な絵文字フォントを知っていたら、是非ダウンロード場所教えてください!</strong></p>
<p>で、ダウンロードしたファイルを解答し <code>/.font/NotoColorEmoji.ttf</code> に設置</p>
<h2 id="WEBフォント"><a href="#WEB%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88">WEBフォント</a></h2>
<p>こちらは絵文字以外のフォントについては、WEBフォントをつかっています。</p>
<p>screenshot を撮る前に head に追加してやれば反映されるはず。</p>
<pre><code class="js"> await page.evaluate(() => {
var style = document.createElement('style')
style.textContent = `
@import url('//fonts.googleapis.com/css?family=M+PLUS+Rounded+1c|Roboto:300,400,500,700|Material+Icons');
div, input, a, p{ font-family: "M PLUS Rounded 1c", sans-serif; };`
document.head.appendChild(style);
document.body.style.fontFamily = "'M PLUS Rounded 1c', sans-serif";
})
</code></pre>
<p>その後、長めに wait させたないとフォントが反映されていないことがあるので注意</p>
<pre><code> await page.waitFor(3000);
</code></pre>
<p>こちらの記事を参考にしています。<br />
<a target="_blank" rel="nofollow noopener" href="https://qiita.com/chimame/items/04c9b45d8467cf32892f">https://qiita.com/chimame/items/04c9b45d8467cf32892f</a></p>
<h2 id="関数実行"><a href="#%E9%96%A2%E6%95%B0%E5%AE%9F%E8%A1%8C">関数実行</a></h2>
<p>lambada で呼び出す関数で、フォントをサーバーにインストールする <code>fc-cache</code> コマンド実行してから、<br />
puppeteer の screenshot などを実行すれば、ちゃんと絵文字が反映されているはずです。</p>
<p>アップロードすれば終了です。</p>
<p><img src="https://qiita-image-store.s3.amazonaws.com/0/6531/54eae76b-6c6d-e567-d400-0c18bc462718.jpeg" alt="yahsan2-ogp-1.jpg" /></p>
<p>めでたし!</p>
<p>serverless を使ったベーシックな puppeteer on lambda は github に置いてあるので必要あれば。<br />
<a target="_blank" rel="nofollow noopener" href="https://github.com/yahsan2/puppeteer-example-on-serverless-lambda">https://github.com/yahsan2/puppeteer-example-on-serverless-lambda</a></p>
<p>この記事のフォント反映バージョンも要望あればで公開しますので、コメントください〜!</p>
<h3 id="poiit というサービスつくっています!"><a href="#poiit+%E3%81%A8%E3%81%84%E3%81%86%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%EF%BC%81">poiit というサービスつくっています!</a></h3>
<p>この記事がもし参考になれば、50円からサポートお願いします〜!<br />
<a target="_blank" rel="nofollow noopener" href="https://poiit.me/yahsan2/"><img src="http://res.cloudinary.com/dcsqopmuq/image/upload/v1545924831/poiit-production/yahsan2/yahsan2-ogp.jpg" alt="yahsan2-ogp-1.jpg" title="画像タイトル" /></a><br />
<a target="_blank" rel="nofollow noopener" href="https://poiit.me/yahsan2/">https://poiit.me/yahsan2/</a></p>
yahsan2