MOテクノロジー

技術をメモしていくブログ

Lambdaからcurlしようとしたらエラー

Lambdaで遊んでるんだけど、ちょっとした検証をしたくてLambdaからhttpリクエストをcurlで出そうとした。ちなみに言語はPython。requestsをimportしてもいいが、zipにしてあげるのがめんどくさかった。
subprocessをimportして、curlをしてみたらエラーが。。。
どうやら標準でインストールされていないらしい。

[ERROR] FileNotFoundError: [Errno 2] No such file or directory: 'curl'
Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 51, in lambda_handler
    subprocess.run(['curl', 'example.com'])
  File "/var/lang/lib/python3.8/subprocess.py", line 489, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/var/lang/lib/python3.8/subprocess.py", line 854, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/var/lang/lib/python3.8/subprocess.py", line 1702, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)END RequestId: 54771e23-e24a-4e75-9de9-fa9729347f06

残念だけど、まあ納得。requestsをimportがいいのかな。
ちなみに/usr/bin配下をみてみた。

alias
arch
awk
base64
basename
bash
bashbug
bashbug-64
bg
ca-legacy
captoinfo
cat
catchsegv
cd
chcon
chgrp
chmod
chown
cksum
clear
comm
command
cp
csplit
cut
date
dd
df
dgawk
dir
dircolors
dirname
du
echo
egrep
env
expand
expr
factor
false
fc
fg
fgrep
fmt
fold
gawk
gencat
getconf
getent
getopts
grep
groups
head
hostid
iconv
id
igawk
info
infocmp
infokey
infotocap
install
jobs
join
ldd
link
ln
locale
localedef
logname
ls
makedb
md5sum
mkdir
mkfifo
mknod
mktemp
mv
nice
nl
nohup
nproc
numfmt
od
p11-kit
paste
pathchk
pgawk
pinky
pldd
pr
printenv
printf
ptx
pwd
read
readlink
realpath
reset
rm
rmdir
rpcgen
runcon
sed
seq
sh
sha1sum
sha224sum
sha256sum
sha384sum
sha512sum
shred
shuf
sleep
sort
sotruss
split
sprof
stat
stdbuf
stty
sum
sync
tabs
tac
tail
tee
test
tic
timeout
toe
touch
tput
tr
true
truncate
trust
tset
tsort
tty
tzselect
umask
unalias
uname
unexpand
uniq
unlink
update-ca-trust
users
vdir
wait
wc
who
whoami
yes

ぐへ。

「情熱プログラマー」を読んだ

「情熱プログラマー」を読んだ。
伝えようとしていることは

  • プログラミングだけではなく、ビジネスの部分を理解しろ
  • 勉強し続けろ
  • 自分を売り込め

のように感じた。まあその通りですよね。

笑った一文

たいていのプログラマは小中学生の頃、どんなグループを作るときも最後になってようやく声がかかるような生徒だった。できるだけ社交的な場面を避け、避けられない時は惨めな思いをする。
出典元:情熱プログラマー ソフトウェア開発社の幸せな生き方

プログラマディスられてて草。

リードレプリカを含むRDSは停止できない

開発・検証環境ではLambdaを使ってRDSを夜間停止している。
ログをみたところ、エラーになっており正常に停止できていないことに気づく。

An error occurred (InvalidDBInstanceState) when calling the StopDBInstance operation: Cannot stop or start a Read-Replica instance: InvalidDBInstanceStateFault
Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 44, in lambda_handler
    done = rds_stop(TARGET_RDS)
  File "/var/task/lambda_function.py", line 87, in rds_stop
    resp = rds.stop_db_instance(DBInstanceIdentifier=rds_identifier)
  File "/var/runtime/botocore/client.py", line 272, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/var/runtime/botocore/client.py", line 576, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.InvalidDBInstanceStateFault: An error occurred (InvalidDBInstanceState) when calling the StopDBInstance operation: Cannot stop or start a Read-Replica instance

そういえばリードレプリカは最近立てたんだった。のでその影響らしい。
公式サイトを確認したところ、リードレプリカを含むRDSは、マスター側もリードレプリカ 側も停止することができなくなるらしい。

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_StopInstance.html

リードレプリカが含まれているか、リードレプリカである DB インスタンスは停止できません。

なるほどなー。

PyCharmをインストールしてみた

Pythonは今までvscodeで書いていたんだけど、仕事でそれなりにコードを書くことが増えそうなのでPyCharmをインストールしてみた。
まだ全然試していないから、ちょう上部だけの感想だけど、
プロジェクト作成時にvenvの設定とかできる。
f:id:komepea:20200223131800p:plain ただvenv使うようなプロジェクトかっていったらそんなこともないので、微妙に使い道ない感じ。
Existing Interpriterを使えば、venvとかの環境を使わないでのプロジェクト作成もできた。

ライブラリとしては、pipとsetuptoolsのみインストールされている。 f:id:komepea:20200223132850p:plain

GUIからライブラリは検索・インストールできる。 まあ便利っちゃ便利。 f:id:komepea:20200223133044p:plain

あといいところとしては、多分デフォルト設定?でPEP8のコーディング規約チェックをしているところ。 f:id:komepea:20200223132110p:plain

コード補完とかはやっぱりいいよね。 多分めちゃくちゃ高機能なんだろうけど、vscodeでよくねって思った。慣れの問題もあるだろうけど。学習コストがね...。
暇なときにちょくちょく使ってみよう。

opensslコマンドでbase64エンコードしたら改行されてしまった

APIの開発を行っていて、curlでリクエストを送る機会があった。
リクエストのイメージとしてはこんな感じ。

curl -k "https://sample-api/v1/users" -X POST -d "id=XXX" -H "Authorization:Basic $(echo -n api-user:EtKWah5ALFQhwPAf7YFx3grY57YQz6kXLh4579MZ
 | openssl base64)"

APIの中身は今回関係ないのでおいといて、AuthorizationヘッダにAPI利用者の情報をbase64エンコードした状態で設定し、アプリ側でデコードして利用者認証をする。
しかし、curlでこのリクエストを投げると「curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)」というエラーになる。
これについて少しハマったのでメモ。
前提として、システム構成はAWSのALBにEC2をぶら下げている。 Webサーバはnginxを使用。

エラーについて調べる

このエラーについてググってみると、ALB - EC2間の通信はhttp1.1を使っているから云々とか、nginx側がhttp2で受けようとしている云々とか出てくる(本当にそういう話なのかどうもしっくりこないけど...)。
とりあえずネットの情報を信じてHTTPのバージョンを指定し、curl -k -http1.1 "https://sample-api/v1/users" -X POST -d "id=XXX" -H "Authorization:Basic $(echo -n user:EtKWah5ALFQhwPAf7YFx3grY57YQz6kXLh4579MZ | openssl base64)"で叩いてみるとリクエスト自体は成功した。じゃあやっぱりそういう問題なのかなとも思ったが、今度は何やらアプリ側で利用者認証ができていない...。

そこでcurlではなくPostmanでAPIを叩いてみると正常に動いた。この場合は利用者認証もできている...。
次に、curlで叩いた時とPostmanで叩いた時でアプリをデバッグしてみる。どうもAuthorizationヘッダの値が違う...?正確にいうと、curlで叩いた時の方がPostmanで叩いた時よりもAuthorizationヘッダが短い。

ということはcurlしたときにはAuthorizationヘッダが正しく送れていないっぽい。

opensslの改行について見ていく

怪しいところはわかってきたので、curlは一旦おいといてopensslでエンコードしている部分を見ていく。

~ $ echo -n "Authorization:Basic $(echo -n api-user:EtKWah5ALFQhwPAf7YFx3grY57YQz6kXLh4579MZ | openssl base64)"
Authorization:Basic YXBpLXVzZXI6RXRLV2FoNUFMRlFod1BBZjdZRngzZ3JZNTdZUXo2a1hMaDQ1NzlN
Wg==~ $

Authorizationヘッダの部分をechoしてみると、出力が改行されている...なぜだ...絶対ここが原因だ...
そこでopensslの改行について調べてみると、似たような症状の人もいた。

opensslでBASE64エンコードされた文字列をdecryptしようとしたら769bytes以上になるとエラーになる件 - だいたいよくわからないブログ

なるほどねー、文字数で改行が行われるわけね。
試しにエンコードする文字列を短くしたら、改行はされなくなった。
原因は分かったので、最初のコマンドに-Aオプションをつけてみる。

~ $ echo -n "Authorization:Basic $(echo -n api-user:EtKWah5ALFQhwPAf7YFx3grY57YQz6kXLh4579MZ | openssl base64 -A)"
Authorization:Basic YXBpLXVzZXI6RXRLV2FoNUFMRlFod1BBZjdZRngzZ3JZNTdZUXo2a1hMaDQ1NzlNWg==~ $

改行されなくなり、リクエストも正常に処理された。

ちなみに、Macに入っているopensslのマニュアルにも書いてあった、

     -A      If the -a option is set, then base64 process the data on one line.

     -a, -base64
             Base64 process the data.  This means that if encryption is taking place, the data is base64-encoded after encryption.  If decryp-
             tion is set, the input data is base64-decoded before being decrypted.

解決までだいぶ遠回りした気がする...

ちなみに上でも書いたこのコマンド。 curl -k -http1.1 "https://sample-api/v1/users" -X POST -d "id=XXX" -H "Authorization:Basic $(echo -n user:EtKWah5ALFQhwPAf7YFx3grY57YQz6kXLh4579MZ | openssl base64)"でリクエストが通ったのは謎。

参考

Enc - OpenSSLWiki

【AWS】DynamoDBの料金(超ざっくり)

初めてDynamoDBを運用することになりそうなので、プロビジョンドモードとオンデマンドモードの料金体系の違いを超ざっくりメモ。

プロビジョンドモード

1秒あたりの書き込みキャパシティーユニット(WCU)と読み込みキャパシティーユニット(RCU)を指定する。
料金は1時間あたりのWCU/RCUの設定値で決まる。(実際の読み込み、書き込みの回数は関係しない)
書き込みキャパシティーユニット (WCU) --> 0.000742USD/WCU
読み込みキャパシティーユニット (RCU) --> 0.0001484USD/RCU

「書き込み、読み込みのサイズがXXバイトまでなら、WCU/RCUを1消費する」みたいなイメージ。
例えば、WCU/RCUが5の場合、1秒間にXXバイト以下の処理を5回実行できる。
正確には処理の特徴(結果整合性やトランザクション)の設定で閾値は変わってくる。詳細は公式サイトで。
プロビジョニング済みキャパシティーの料金 - Amazon DynamoDB | AWS
オートスケールの設定もできる。

オンデマンドモード

WCUやRCUの設定の必要はなく、読み込みリクエスト単位で課金される。
書き込みリクエスト単位 --> 書き込みリクエスト 100 万単位あたり 1.4269USD
読み込みリクエスト単位 --> 読み込みリクエスト 100 万単位あたり 0.285USD

「書き込み、読み込みのリクエストがXXバイトまでなら、読み書きリクエストが1単位必要」みたいなイメージ。
こちらも処理の特徴(結果整合性やトランザクション)の設定で閾値は変わってくる。詳細は公式サイトで。
オンデマンドキャパシティーの料金 - Amazon DynamoDB | AWS

どっちのモードがいいかは悩みどころ。
とりあえず今の用途としては、Read/Writeの回数もそこまで多くはないし、オンデマンドでいいかなって感じ。
WCU/RCUの検討やオートスケールの検討をしなくてもいいっていうのも大きい。

参考

DynamoDBのオンデマンドとプロビジョニングの料金を比較をしてみた #reinvent | Developers.IO

【DB】あえて正規化しない設計の学び

新年ですな。
今新規でAPIの設計を進めているが、データ設計で学びがあったのでメモ。

API仕様(超ざっくり)

  • 前提
    API利用者の情報を持つ「認証情報テーブル」と、
    申し込み情報を持つ「申し込み情報テーブル」がある。

  • リクエストパラメータ
    ?Id=XXX&Key=YYY&applicationId=ZZZ
    ※実際はIdとKeyは暗号化するけど細かいことは置いておく。

  • 処理概要
     1. 認証情報テーブルのIdとKeyでAPI利用者認証。
     2. 申し込み情報テーブルから、リクエストパラメータのapplicationIdが一致するカラムの情報を取得。
     3. レスポンスパラメータは利用者ごとに異なるため、その利用者に沿った項目を返却。

  • 申し込み情報テーブル

applicationId Name Email Address PhoneNo Status
1 tanaka tanaka@mail.com hoge 090-XXXX-XXXX 1
2 yamane yamane@mail.com piyo町 080-XXXX-XXXX 2
  • 課題
    要件通り、API利用者ごとにレスポンスを変える必要がある。
    認証情報テーブルの設計をどうするかが問題。

当初案

申し込み情報テーブルと同じカラムを作っておいて、返却する・しないのフラグを立てる。 この場合、利用者がhogeの場合は全項目を返却する。
piyoの場合は、Name,Email,Statusを返却する。

Id Key Name Email Address PhoneNo Status
hoge key1 1 1 1 1 1
piyo key2 1 1 0 0 1

メリット:テーブルを見ただけでどの項目を返却するかがわかりやすい。
デメリット:カラム数が多いと見づらい。返却項目が増えた場合に、いちいちALTER TABLEしないといけない。

最終案

Responsesカラムに返却項目をカンマ区切りでつなげる。

Id Key Responses
hoge key1 Name,Email,Address,PhoneNo,Status
piyo key2 Name,Email,Address

メリット: 返却項目が増えてもカラム追加する必要がない。
デメリット:INSERTするときのチェックなどがめんどう。プログラム側でResponsesカラムのパースなりが必要。

所感

自分としては上記の当初案もしくは、ResponsesIdみたいなカラムを作成しそのIDに応じて返却する項目情報を別のファイルとかに書き出すとかでもいいのかな〜みたいに思っていたが、今回のケースでは利用のされ方や、Responses登録時の運用手順を想像した上で、最終案がいいのではというアドバイスをいただいた。
本来は正規化するべきなので、安易に最終案の形にするのはバッドだと思うが、場合によってはありなんだな〜と思った。