Lambda レイヤーを Amazon Linux 2で作成する
最近Discord上で動く時差変換botを作って、lambdaで動かすようにした。
GitHub - kumo2kumo/time_converter
その際lambda_function.pyで
Unable to import module 'lambda_function': No module named 'nacl._sodium
と出て読み込めない。
lambdaのランタイムはpython3.10に指定。
やり方は
Python Lambda 関数の「モジュールをインポートできません」エラーを解決する | AWS re:Post
を参考にした。
まずリンクの通りIAMポリシー作成→ロールにアタッチー>ロールをEC2にアタッチ
AmazonLinux2は初期でPython 3.7.16
リンクに従ってamazon-linux-extraでインストールしようとしても
amazon-linux-extras list | grep python
44 python3.8 available [ =stable ]
の通り3.8までしかできない。
ので3.10は自身でインストールする。
これを参考に
Amazon Linux 2にPython3.10をインストール | fragment
完了したらpynaclをインストールしてlayerに追加
mkdir python
python3.8 -m pip install pynacl -t python/
zip -r layer.zip python
aws lambda publish-layer-version --layer-name hoge --zip-file fileb://layer.zip --compatible-runtimes python3.10 --region ap-north-east-1(tokyo regionの場合)
最後にlambdaでhoge layerを追加
で完了できた😄
AWS lambdaを使ってみる
元々タスクスケジューラで動かしていたDiscord botをAWS lambdaに移行してみたので、メモ📝
1,ライブラリを特定のフォルダにインストールする
↓の場合my_lambda を指定
py -m pip install -r requirements.txt -t ./my_lambda
2,lambda_function.pyに処理を記載。 function名はlambda_functionとする。
3,lambda_function.pyもmy_lambda フォルダに移動してzipで固める
4、aws lambdaにアップロードする
環境変数をコード上で呼び出すためには
import os cousumer_key = os.environ['CONSUMER_KEY']
のように書く AWS Lambda 環境変数の使用 - AWS Lambda
コードはこんな感じ。特定のTwitterアカウントのツイートを取得してbotで表示するもの
import tweepy import requests from discordwebhook import Discord import os consumer_key = os.environ['CONSUMER_KEY'] consumer_secret = os.environ['CONSUMER_SECRET'] access_token = os.environ['ACCESS_TOKEN'] access_secret = os.environ['ACCESS_SECRET'] webhook_url = os.environ['WEBHOOK_URL'] class TwitterApi: def get_latest_tweet(self, account_name): auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_token, access_secret) api = tweepy.API(auth, wait_on_rate_limit=True) tweet = api.user_timeline(id=account_name, count=1)[0] screen_name = tweet.user.screen_name return tweet.text def lambda_handler(event, context): api = TwitterApi() text = api.get_latest_tweet("@xxxxxxx") discord = Discord(url=webhook_url) discord.post(content=text)
6,デプロイする&テストしてみる。
ここで
401 Unauthorized\n32 - Could not authenticate you
が出る。あら認証されてない、おや(;'∀')
7,Twitterの開発者ポータル見ると
This App has violated Twitter Rules and policies. As a result, it can no longer be accessed. For assistance, submit a support ticket
のエラーが。プロジェクト作り直して諸々のキーを再取得
8,環境変数変えてテスト→成功
9,トリガーをイベントで設定
今回は毎日0時に投稿したいので、
cron(00 15 * * ? *)
と記載。この時間はUTC time
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-cloudwatchevents-expressions.html
10,無事0時に表示、おめでとう㊗
めっちゃ便利やないか、、、今までタスクスケジューラでやってたから、PC起動していないときは実行されなかったんだよね。 やってみようと思ったのは応用情報のシステムアーキテクチャ解いててFaaSについて出てきて、ふむFaaSでbot実行できるのでは? FaaSには
AWS Lambda
Google Cloud Functions
Microsoft Azure Functions
などがあるのね、awsアカウント持ってるしやってみよう、ってなったから。 次はもう一個Discord上で動かしたい処理があるからそれも作ってみよー
batファイルをタスクスケジューラで動かす時
先日twitterの投稿をdiscordに自動で投げるbotをpythonで作っていたのだが、
作ったbatファイルをタスクスケジューラに設定しても実行してくれず、さて結局どうしたかっていう話。というかbatファイルって初めて書いた、今まで拡張子がbatって何だと思っていたーシェルファイルのwindows版と理解(ふわっ
結論から言うと
こちらの通りで、開始のところにbatファイルがあるフォルダの絶対パスをきちんと書きましょうー
ということ
自分は最初はプログラム/スクリプトのところにbatファイルまでの絶対パス(C:\hoge\main.bat)を書いていたのですが、動かなかったのですねー
それはどうやら開始欄が空欄だとC:\Windows\system32\C:\hoge\main.bat
と思われてしまうらしく、そんなんないねんとお怒りになっていた模様
無事開始欄にパスを書いて、botが毎日起動してますーやった!
余談ですが10月から大学理工学部の科目等履修生にお邪魔することにした。
回路とか離散数学とか興味の向くままニューワールドに飛び込んでいくよ、どこに行く自分
【試して理解】Linuxのしくみ 読んだメモ
忘れないようメモ📝LinuxのシステムをC言語でプログラム書いて色々覗いてみようという本。 個人的に見よう見まねでCを書けて楽しかったし、メモリ操作とかあってハードウェアに近い言語なんだなって体感した。 Cをもう少しやってみたいので、東大でCの基礎を学ぶ講義が公表されているのでこれをやってみようと思います。
1
- Linuxでデバイスを操作する処理をデバイスドライバというプログラムにまとめて、プロセスからデバイスには、このドライバを介してアクセスしている
- BIOS, UEFI: ハードウェア組み込みのソフトウェア。ハードウェアの初期化処理をする
2. ユーザーモード
- プロセスがカーネルの助けが必要な時に、処理を依頼することをシステムコールという。
- ex. プロセス生成・削除、メモリ管理、プロセス間通信、ネットワーク、ファイルシステム操作、ファイル操作 ‐ straceでprint("hello world")のシステムコールを見てみる。C:29行、Python: 514行!Σ(・□・;)
- %systemが数十のように大きな値になっているときは、システムの負荷が高い状態かも。
- 各高級言語ごとに、システムコールのラッパー関数をOSは用意している。
- OSが提供するプログラムは色々ある。コンパイラgcc, スクリプト言語実行環境:pythonなども。
3. プロセス管理
- プロセス生成は2種類
- 同じプログラムの処理を複数プロセスに分けて処理
- fork()(内部的にはclone()):発行したプロセスを元に、新たにプロセスを1つ生成。
- 全く別のプログラムを生成
‐ プロセス数が増えるのではなく、あるプロセスを他のプロセスで上書きする
- execve():
- プログラム実行の流れ:プログラムファイルの情報(補助的情報・コード領域・データ領域)に基づいてメモリ上にマップされる → エントリポイントからプログラムを実行する
- プロセス終了時は_exit()関数を使用。直接呼び出すことは少なく、Cならexit()呼び出しで終了
- 同じプログラムの処理を複数プロセスに分けて処理
4. システムスケジューラ
- 論理CPU上である瞬間に動作できるプロセスは1つのみ
- 複数プロセス実行中の場合、途中でプロセスを切り替えている→切り替えをコンテキストスイッチという
- プロセスのスリープ状態時はCPUはアイドリング状態(使っていない)
- レイテンシ:処理の終了までの所要時間
- スループット:単位時間当たりの総仕事量
- 論理CPU0と、論理CPU/2の番号のものは独立している
- プロセス数を論理CPU数より多くしても、スループットは上がらない
- nice(): 特定のプロセスに優先度を設定する。-19(高い)~20(低い)。優先度を上げられるのはrootのみ
5. メモリ管理
- メモリ使用量が増えて身動きが取れない事:OOM(out of memory), メモリ管理システムはこの時適当なプロセスをkillしてメモリを開放する機能がある。
- sysctlのvm.panic_on_oomが0→デフォルト、killer発動, 1->OOM時にシステム強制終了
- プログラムはメモリが断片化していると、使用できないこともある
- 仮想記憶:物理メモリにプロセスから直接アクセスするのではなく、仮想アドレスを用いて間接的にアクセスすること。仮想アドレス=プロセスから見えるメモリのアドレス。プロセスから物理アドレスに直接アクセスする方法はなし。
- 仮想アドレス≠物理アドレス
- 仮想アドレスと物理 -アドレスの対応表をページテーブルという。サイズは4KB(x86_64アーキテクチャ).
- メモリ割り当て方法
- mmap関数:ページ単位で仮想メモリ取得、大きめメモリをプールしておく→その後malloc関数:バイト単位でメモリ取得
- ファイルマップ:mapp()を所定の方法で呼び出すことで、ストレージデバイス内の該当ファイルをメモリに呼び出す→その空間を仮想アドレス空間にマッピングできる
- デマンドページング:
- コピーオンライト;fork()時は、親プロセスのページテーブルを子プロセスにコピーする(一緒の物理アドレスを指している)この時書き込み権限は無効化されている
- スワップ:ストレージデバイスの一部を一時的にメモリの代わりとして使用する。物理メモリが枯渇した際、使用の一部をストレージに退避させる。メモリを退避させる領域をスワップ領域(windowsでは仮想メモリ)、退避させることをスワップアウトという。取り戻すことをスワップイン、合わせてスワッピングという
- ヒュージページ;プロセスのべージテーブルに必要なメモリ量を減らせる、データベースなど仮想メモリを大量に使用するときに検討すると吉
6. 記憶階層
- コンピュータの動作の流れ
- レジスタのメモリのやり取りのところで時間がかかる→キャッシュメモリを利用
- キャッシュメモリ:基本CPU内にある。メモリのデータをキャッシュメモリにキャッシュする
- ページキャッシュ:ストレージのデータをメモリにキャッシュすること ‐ プロセスがファイルのデータを読み出す場合、まずカーネルのメモリ上のページキャッシュ領域にコピーして情報を保持→そのデータをプロセスのメモリにコピーする.再度呼び出された場合早く呼び出せる。
- ハイパースレッド機能:CPUコア内のレジスタなどを複数用意して、それぞれを論理CPUと認識されるようにするハードウェア機構スループットが単純に倍になるという訳でもない。
- /sys/devices/system/cpu/cpuNum/topology/thread_siblings_list: ペアとなるスレッドの表示
7. ファイルシステム
- どこにどんなデータがあり、どこに空き領域があるか管理する仕組み
- linuxは複数のファイルシステムを扱える(ext4, XFS, Btrfs... ストレージデバイス上に存在する)
- データ(データの中身・内容)とメタデータ(種類・時刻・権限情報)の2種類がある
- ファイルシステムごとに使用できる容量を制限する機能があり、クォータという ‐ ユーザクォータ、ディレクトリクォータ…
- システム不整合を防ぐ方法
- tmpfs: メモリ上に作成するファイルシステム、再起動後はデータが消えている
- mount | grep ^tmpfs
- freeコマンドのsharedフィールドの値はtmpfsによって使用されているメモリ量(KB)
- ネットワークファイルシステム:リモートホスト上のファイルにアクセス。windowsはcifs, UNIX系はnfs
8. ストレージデバイス
- HDD
- SSD
使ったコマンド💻
- strace: プロセスが呼び出すシステムコールをみる
- sar: プロセスがユーザーモードかカーネルモードのどちらで実行しているのかみる
- sar -P ALL 1(1秒ごとに見る) sar -P ALL 1 1(1秒ごとに1回測定)
- ユーザーモード→%userと%niceの合計 ‐ カーネルモード→%system
- sar -q 1 1: runq-szフィールド→実行中or実行待ちプロセス数
- sar -r 1: 1秒ごとのメモリ状況。kbmemused->物理メモリ使用量、kbswpused->スワップ領域の使用量、kbpgtbl->ページテーブルに使用している量, kbcached->ページキャッシュの総量(kb)
- sar -W 1: ``ごとのスラッシング確認、pswpin/sがスワップイン ‐ sar -d -p 1: ストレージデバイスに対するI/O量
- ldd: プログラムがどのライブラリにリンクしているかみる
- ldd /bin/echo ‐ readelf
- readelf -h /bin/sleep: 開始アドレスを表示
- /proc/pid/maps: プログラム実行時に作成されたプロセスのメモリマップ表示、仮想アドレスを表示
- taskset: 指定の論理CPUで動作させる
- task -c 0 ./sched
- ps ax: 動かしているプロセスの確認
- STAT: R->実行or実行待ち状態、S, D->スリープ状態(シグナルによって実行状態に戻るのがS), Z-> ゾンビ状態(親プロセスの終了待ち)
- ps -eo pid,comm,etime,time: 経過時間(elapsed)と使用時間(time)
- grep -c processor /proc/cpuinfo:論理CPUの数
- timeコマンド:CPUの経過時間(real)と使用時間(user + sys,全コアの合計使用時間)がみれる ‐ time taskset -c 0 ./sched 1 10000 10000
- nice -n 5 echo hello: 優先度5でecho helloする
- free: memory容量確認(kバイト)
- swap --show: システムのスワップ領域確認
- cat /sys/kernel/mm/transparent_hugepage/enabled: トランスペアレントヒュー次ページの使用を確認
- df: ファイルシステムのストレージ使用量
- /proc/pid: 各プロセスの情報を得る
- maps, cmdline, stat
- /procにはその他ハード情報が盛りだくさん、詳しくはman procで ‐ /sys: procのようなカーネルのプロセスに関するもの以外の雑多な情報
prismaが生成するqueryをログに出す
prisma.service.tsを変更
import { INestApplication, Injectable, OnModuleInit, Logger, } from '@nestjs/common'; import { PrismaClient, Prisma } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient<Prisma.PrismaClientOptions, Prisma.LogLevel> implements OnModuleInit { private readonly logger = new Logger(PrismaService.name); constructor() { super({ log: ['query', 'info', 'warn', 'error'] }); } async onModuleInit() { this.$on('query', (event) => { this.logger.log( `Query: ${event.query}`, `Params: ${event.params}`, `Duration: ${event.duration} ms`, ); }); this.$on('info', (event) => { this.logger.log(`message: ${event.message}`); }); this.$on('error', (event) => { this.logger.log(`error: ${event.message}`); }); this.$on('warn', (event) => { this.logger.log(`warn: ${event.message}`); }); await this.$connect(); } async onModuleDestroy() { await this.$disconnect() } async enableShutdownHooks(app: INestApplication) { this.$on('beforeExit', async () => { await app.close() }) } }
graqhQLのtutorialやってみたメモ📝
situation
最近NestJSとprisma, graphQLを使ったAPI構築を勉強していて、 いまいちprismaとgraphQLの繋がりがふわふぁっとしていたので、howtogrqphQLという公式のtutorialをやってみた。 やっぱり公式はわかりやすい。。。 リマインドのために、サイトの訳(By 翻訳サイト)と自分の所感の雑記
memo
TS関係
@ts-ignore
コメントをすると、次の行の型チェックが無視される- index.js や index.ts はnodeJSではデフォルトのエントリーポイントと見なされる、最初に読み込まれるよ
- 実行コマンド:
npx ts-node src/script.ts
graqhQLのschemaって?
- GraphQL schemas はGraphQL Schema Definition Language (SDL)の中で定義される
- それぞれのGraphQL schemaは3つのオリジナルのroot typeを持つよ: Query, Mutation, Subscription
- GraphQLは5このデフォルトのscalar typesがあるよ: Int, Float, String, Boolean and ID
Nexusライブラリについて
- type-safe GraphQL schemasをcode-first approachで生成するライブラリ
①Nexusを使って、graphQLのtypeやroot object typesを記述していく。 objectTypeはGraphQLの中で新しいtypeを作るために作られる。 ↓Userタイプの例
export const User = objectType({ name: "User", definition(t) { t.nonNull.int("id"); t.nonNull.string("name"); t.nonNull.string("email"); } })
②コマンドnpx ts-node --transpile-only src/schema
でGraphQL SDL とtypesを生成する。-> typeとかQuery, Mutationは見た目的に完成
③追加されたフィールドに対応するresolverを実装する
resolverって?
- resolverはGraphQLフィールドの実装です。各タイプのすべてのフィールド(ルートタイプを含む)は、そのフィールドに対応するデータを返す役割を持つresolverが用意されています。
- (´-`).。oO: resolverでデータのCRUD処理を実際にするイメージ。 例えばLinkというTypeとfeedというQueryがあった場合
type Link { createdAt: DateTime! description: String! id: Int! postedBy: User url: String! voters: [User!]! } type Query { link(id: Int!): Link }
export const LinkQuery = extendType({ type: "Query", t.field("link", { type: "Link", args: { id: nonNull(intArg()) }, // ここまでlinkという名前のqueryを定義 // ここから実際にlinkQueryがどういう処理をするか定義(Prisma利用) resolve(parent, args, context, info){ // console.log(args) //{ id: 1 } const { id } = args return context.prisma.findUnique({ where: { id: id } }) } }) },
- GraphQLサーバーがすべきことは、クエリに含まれるフィールドに対してすべてのresolverを呼び出し、クエリの形状(この場合Link)に従ってレスポンスをパッケージ化することである。このように、クエリーの解決はresolverの呼び出しをオーケストレーションするプロセスに過ぎないのである。
- (´-`).。oO: GraphQLのQueryは該当するresolverを呼び出して、うまくまとめて返してくれるのね
- all GraphQL resolver functionsは常に4つの引き数を受け取る: parent, args, context, info
- contextとは?: context 引数は JavaScript のプレーンなオブジェクトで、リゾルバーチェーン内のすべての リゾルバーが読み込みと書き込みを行うことができる。したがって、これは基本的にリゾルバが通信するための手段である
- Prisma Clientをcontextにぶっこむと、resolver内からPrisma Clientにアクセスできます!
- Prisma Clientには、データベースに対するクエリーを実行するために必要なものがすべて含まれています。
- Prismaのクエリは、非同期なのでPromiseオブジェクトを返します。
- parentのくだり
PrismaとgraphQLの関係
- (´-`).。oO: GraphQLがこーゆー名前のqueryもしくはmutationの処理あるよ、それは何を返すよって、宣言して、実際そのデータのCRUD処理を行うのがresolverで、データベースを使うときにはprismaを使ってCRUD処理のやりとりをdbとしてるって感じ。
- (´-`).。oO: 例えばこーゆタイプ(User)があるよ、その中でこういうリレーションがあるよって示しているのがSDLで、実際にそのリレーションのデータのやり取りをするのがresolveって感じ
- objectType内でもrelationがあったらresolveする。↓
// in User.ts>User = objectType t.nonNull.list.nonNull.field("links", { // 1 type: "Link", resolve(parent, args, context) { // 2 return context.prisma.user // 3 .findUnique({ where: { id: parent.id } }) .links(); }, });
- 上について公式曰く: リンクフィールドのリゾルバは自明ではないので、データベースから返されるUserオブジェクトが自動的にリンクタイプを含まないため、GraphQLは自動的にそれを推論することができません。このため、Userタイプの他のフィールドとは異なり、リンクのリゾルバを明示的に定義する必要があります。
- 上のコードの
.links()
について:prisma.shcemaのUser modelに記述している
links Link[] @relation(name: "PostedBy")
のlinksのことを指しているっぽい
- (´-`).。oO: prisma shemaに書いても、objectTypeに記述しないと、graphQLには反映されないんだなぁ
- 多対多のrelationならそれぞれのobjectTypeにその旨記載
その他
- idArg: string, intArg(): intで詰まる
- [User!]!の意味:空のリストか、NULLでないUserオブジェクトのみを含むリストを受け取るの意
- pagination: Prisma Client APIは、findManyクエリの追加オプションとしてskipとtakeの引数を受け取り、それに応じてリンクレコードを返します。