Miscellaneous notes

主に技術的な雑記的な

2018年の抱負とか

31歳になりました。30台になると、自分がおっさんであること、もうどうひっくり返っても若手とは言い難いことを自覚しますね。それ以外には特に何かが変わるわけでもないけど、自分の周りの活躍してる人達っていうのは大体30半ばくらいからの方が多くて、きっとその年代はあっという間なので、去年みたいに年始に受けた精神的なダメージを年の半ばまで引きずってウジウジしてると後悔することになるので、今年も同じダメージを既に受けかけてますがもうそれはそれとして自分の中で切り離してやっていける心の強さは身についた気がするので、今年はスタートから勢い良くいきたいと思います。

2017年の振り返りと2018年の抱負

仕事

そんな感じで、去年は年間通しても良いパフォーマンスは出せていなかった。さすがに仕事で迷惑をかけたりはしなかったけれど、もっとできることは全然一杯あったと思うので今年はもっと色々結果で示していければと。とりあえず、今やってるやつを春先くらいに上手いこと世に送り出す。色々面倒なこともあるけども、界隈の力関係をひっくり返したい気持ちで頑張りたい。

技術

去年はあまり新しい領域へチャレンジできなかった。反面、メインの領域であるクラウドとかその辺については案件の多い会社でいくつか濃いやつに関わったりそれ以外も近い位置で見られたこと、サービス開発では自分の思うように開発をさせてもらったことで、今まで微妙に繋がりきっていなかった部分が繋がって自分の中で上手く理論が組み上がった感じがしていて、元々得意だったWeb系やServerlessとかその辺については大体何が来ても大丈夫と思える程度には仕上がったと思う。この領域では「インテグレーターの中では一番」と言い張れるように引き続き頑張る。

marcy.hatenablog.com

で、それはそれとしてプライベートで新しい領域にチャレンジすることは全然できていないので、そっちは反省して頑張らないといけない。去年Rustをちょっと書いたらクソコードを生産したり想像以上に苦労してしまったりしたので、ゆるふわなスクリプト言語以外で一つまともに書けるようになりたい。それこそRustとか。ダメ人間なので特に具体的な目的もなくとにかく勉強するということができないので、Rustとかで書くことが妥当であると思われるような何かほしいものを見つけて一個ちゃんと完成させたい感じ。ミドルウェアとか作ってみたいけど、インテグレーターだとそれを実戦投入するの無理ゲーなんだよなぁ・・・なんか良いのないだろうか。

あと、両極端なんだけどミドルウェアと同じように自分で作れていなくてカバーしきれてないフロントエンドの方もNode.jsには抵抗なくなったので、自分でちゃんと何か作りたい。これは仕事で作った方が良いよなと思っている所があるので勉強してサクッと作れるようになって実戦投入したい。したいというか、これはMustでこれすらもできないようならホントダメだと思うのでやる。

まとめると得意領域が上手く組み上がった実感があるので今年は得意じゃない領域をしっかりやっていきたい。

その他

丸一年リモートワークして色々課題(基本的にダメ人間である私個人の問題)が見えてきていて、とりあえずは対策の一つとしてずっと自宅に居ることが良くないので今年はコワーキングとか契約して外で仕事する時間を増やす。

あと、会社で受けるストレスがなくても外での刺激が必要だと分かったので、来年も東京のカンファレンスで話せるように引き続き頑張りつつ、それだけではもたないので、相変わらず札幌で面白そうな集まりは全然ないので、去年は全然できなかった自分でやる活動を頑張る。

去年は社費でre:Inventに行かせてもらって、ハッカソンで技術的にはある程度頑張れるけど英語が壊滅的と分かったので、英語もやらないと。あと、今年もre:Invent行きたいし、他のカンファレンスとかも行きたいけど、会社だけに期待するのも違うと思うので自費で行ける程度に稼ぎたいのでお仕事ください。 (一応、個人事業もまだやってます。有名無実じゃなく一応仕事は完全に無くなってはないんだけど、普段東京に居ないので話があっても次に会うのがいつか分からないから中々上手く仕事に繋がらないというのも去年の学び・・・)

AWS LambdaのOS情報を色々見てみる 〜Lambdaのインスタンスガチャを検証する〜

この記事は、Serverless Advent Calendar 20日目の記事です。

qiita.com

もっと他のネタを書く予定だったのですが、年内タスクが沢山積んでてヤバいので去年わりと好評だったLambdaのリバースエンジニアリング?をまたやってみたいと思います。

marcy.hatenablog.com

OS情報の集め方

Lambdaの実体がコンテナであることは周知の事実なので、OS情報というには語弊がある部分もあるのですが、コンテナからもホストOSの情報が部分的には見られるので他に良い呼び方も思いつかなかったんで、とりあえずOS情報と呼称します。

OSの情報を集めるのは、ChefのOhaiしかりServerspec(Specinfra)のHost Inventoryしかり、古今東西泥臭くコマンドを叩いて集めると相場は決まっています。余談ですが、ホントこういうのをやってくれるライブラリは偉大ですよね・・・!

なので、こんなFunctionをServerless Frameworkでデプロイします。

import subprocess


def osdata(event, context):
    return dict([(cmd, subprocess.getoutput(cmd).split('\n')) for cmd in event])

serverless.yml はこんな感じ。簡単ですね!

service: lambda-osdata

provider:
  name: aws
  runtime: python3.6

functions:
  osdata:
    handler: handler.osdata

これをこんな感じで思いついたコマンドを列挙するyamlを用意して

- env
- uptime
- uname -a
- df -aTh
- cat /etc/system-release
- cat /proc/cpuinfo
- cat /proc/meminfo
- ps auxf
- id
- cat /etc/passwd
- ulimit -a
- /sbin/ip a
- /sbin/ip r
- netstat -av

このように叩くと結果がJSON Objectで得られます。

$ sls invoke -f osdata -p data.yml

結果①

{
    "env": [
        "AWS_LAMBDA_FUNCTION_VERSION=$LATEST",
        "AWS_SESSION_TOKEN=xxxxxxx",
        "AWS_LAMBDA_LOG_GROUP_NAME=/aws/lambda/lambda-metadata-dev-metadata",
        "LAMBDA_TASK_ROOT=/var/task",
        "LD_LIBRARY_PATH=/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib",
        "AWS_LAMBDA_LOG_STREAM_NAME=2017/12/20/[$LATEST]0a28fe46a1de4e5c8258fe00ee63cd9c",
        "AWS_EXECUTION_ENV=AWS_Lambda_python3.6",
        "AWS_XRAY_DAEMON_ADDRESS=169.254.79.2:2000",
        "AWS_LAMBDA_FUNCTION_NAME=lambda-metadata-dev-metadata",
        "PATH=/var/lang/bin:/usr/local/bin:/usr/bin/:/bin",
        "AWS_DEFAULT_REGION=us-east-1",
        "PWD=/var/task",
        "AWS_SECRET_ACCESS_KEY=xxxxxxxx",
        "LAMBDA_RUNTIME_DIR=/var/runtime",
        "LANG=en_US.UTF-8",
        "AWS_REGION=us-east-1",
        "TZ=:UTC",
        "AWS_ACCESS_KEY_ID=xxxxxxx",
        "SHLVL=1",
        "_AWS_XRAY_DAEMON_ADDRESS=169.254.79.2",
        "_AWS_XRAY_DAEMON_PORT=2000",
        "PYTHONPATH=/var/runtime",
        "_X_AMZN_TRACE_ID=Root=1-5a3a99f7-0b93ba80006cbb7149758be4;Parent=556e4185655d4a61;Sampled=0",
        "AWS_SECURITY_TOKEN=xxxxxxx",
        "AWS_XRAY_CONTEXT_MISSING=LOG_ERROR",
        "_HANDLER=handler.metadata",
        "AWS_LAMBDA_FUNCTION_MEMORY_SIZE=1024",
        "_=/usr/bin/env"
    ],
    "uptime": [
        " 17:12:23 up  2:04,  0 users,  load average: 0.00, 0.00, 0.00"
    ],
    "uname -a": [
        "Linux ip-10-39-53-71 4.9.62-21.56.amzn1.x86_64 #1 SMP Thu Nov 16 05:37:08 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux"
    ],
    "df -aTh": [
        "Filesystem     Type  Size  Used Avail Use% Mounted on",
        "/dev/xvda1     ext4   30G  3.1G   27G  11% /",
        "/dev/xvda1     ext4   30G  3.1G   27G  11% /var/task",
        "/dev/xvda1     ext4   30G  3.1G   27G  11% /dev",
        "/dev/loop2     ext4  526M  440K  514M   1% /tmp",
        "none           proc     0     0     0    - /proc",
        "/dev/xvda1     ext4   30G  3.1G   27G  11% /proc/sys/kernel/random/boot_id",
        "/dev/xvda1     ext4   30G  3.1G   27G  11% /var/runtime",
        "/dev/xvda1     ext4   30G  3.1G   27G  11% /var/lang"
    ],
    "cat /etc/system-release": [
        "Amazon Linux AMI release 2017.03"
    ],
    "cat /proc/cpuinfo": [
        "processor\t: 0",
        "vendor_id\t: GenuineIntel",
        "cpu family\t: 6",
        "model\t\t: 63",
        "model name\t: Intel(R) Xeon(R) CPU E5-2666 v3 @ 2.90GHz",
        "stepping\t: 2",
        "microcode\t: 0x3b",
        "cpu MHz\t\t: 2899.875",
        "cache size\t: 25600 KB",
        "physical id\t: 0",
        "siblings\t: 2",
        "core id\t\t: 0",
        "cpu cores\t: 1",
        "apicid\t\t: 0",
        "initial apicid\t: 0",
        "fpu\t\t: yes",
        "fpu_exception\t: yes",
        "cpuid level\t: 13",
        "wp\t\t: yes",
        "flags\t\t: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt",
        "bugs\t\t:",
        "bogomips\t: 5800.07",
        "clflush size\t: 64",
        "cache_alignment\t: 64",
        "address sizes\t: 46 bits physical, 48 bits virtual",
        "power management:",
        "",
        "processor\t: 1",
        "vendor_id\t: GenuineIntel",
        "cpu family\t: 6",
        "model\t\t: 63",
        "model name\t: Intel(R) Xeon(R) CPU E5-2666 v3 @ 2.90GHz",
        "stepping\t: 2",
        "microcode\t: 0x3b",
        "cpu MHz\t\t: 2899.875",
        "cache size\t: 25600 KB",
        "physical id\t: 0",
        "siblings\t: 2",
        "core id\t\t: 0",
        "cpu cores\t: 1",
        "apicid\t\t: 1",
        "initial apicid\t: 1",
        "fpu\t\t: yes",
        "fpu_exception\t: yes",
        "cpuid level\t: 13",
        "wp\t\t: yes",
        "flags\t\t: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt",
        "bugs\t\t:",
        "bogomips\t: 5800.07",
        "clflush size\t: 64",
        "cache_alignment\t: 64",
        "address sizes\t: 46 bits physical, 48 bits virtual",
        "power management:",
        ""
    ],
    "cat /proc/meminfo": [
        "MemTotal:        3855844 kB",
        "MemFree:         3328804 kB",
        "MemAvailable:    3558848 kB",
        "Buffers:           27780 kB",
        "Cached:           322744 kB",
        "SwapCached:            0 kB",
        "Active:           238224 kB",
        "Inactive:         188640 kB",
        "Active(anon):      76260 kB",
        "Inactive(anon):      128 kB",
        "Active(file):     161964 kB",
        "Inactive(file):   188512 kB",
        "Unevictable:           0 kB",
        "Mlocked:               0 kB",
        "SwapTotal:             0 kB",
        "SwapFree:              0 kB",
        "Dirty:                 0 kB",
        "Writeback:             0 kB",
        "AnonPages:         76180 kB",
        "Mapped:            30912 kB",
        "Shmem:               140 kB",
        "Slab:              72332 kB",
        "SReclaimable:      36756 kB",
        "SUnreclaim:        35576 kB",
        "KernelStack:        2284 kB",
        "PageTables:         2904 kB",
        "NFS_Unstable:          0 kB",
        "Bounce:                0 kB",
        "WritebackTmp:          0 kB",
        "CommitLimit:     1927920 kB",
        "Committed_AS:     413576 kB",
        "VmallocTotal:   34359738367 kB",
        "VmallocUsed:           0 kB",
        "VmallocChunk:          0 kB",
        "AnonHugePages:         0 kB",
        "ShmemHugePages:        0 kB",
        "ShmemPmdMapped:        0 kB",
        "HugePages_Total:       0",
        "HugePages_Free:        0",
        "HugePages_Rsvd:        0",
        "HugePages_Surp:        0",
        "Hugepagesize:       2048 kB",
        "DirectMap4k:       47104 kB",
        "DirectMap2M:     1787904 kB",
        "DirectMap1G:     2097152 kB"
    ],
    "ps auxf": [
        "USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND",
        "475          1  0.0  0.4 170508 18996 ?        Ss   16:02   0:00 /var/lang/bin/python3.6 /var/runtime/awslambda/bootstrap.py",
        "475        597  0.0  0.0 117184  2352 ?        R    17:12   0:00 ps auxf"
    ],
    "id": [
        "uid=475(sbx_user1072) gid=474 groups=474"
    ],
    "cat /etc/passwd": [
        "root:x:0:0:root:/root:/bin/bash",
        "bin:x:1:1:bin:/bin:/sbin/nologin",
        "daemon:x:2:2:daemon:/sbin:/sbin/nologin",
        "adm:x:3:4:adm:/var/adm:/sbin/nologin",
        "lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin",
        "sync:x:5:0:sync:/sbin:/bin/sync",
        "shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown",
        "halt:x:7:0:halt:/sbin:/sbin/halt",
        "mail:x:8:12:mail:/var/spool/mail:/sbin/nologin",
        "uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin",
        "operator:x:11:0:operator:/root:/sbin/nologin",
        "games:x:12:100:games:/usr/games:/sbin/nologin",
        "gopher:x:13:30:gopher:/var/gopher:/sbin/nologin",
        "ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin",
        "nobody:x:99:99:Nobody:/:/sbin/nologin",
        "rpc:x:32:32:Rpcbind Daemon:/var/cache/rpcbind:/sbin/nologin",
        "ntp:x:38:38::/etc/ntp:/sbin/nologin",
        "saslauth:x:499:76:\"Saslauthd user\":/var/empty/saslauth:/sbin/nologin",
        "mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin",
        "smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin",
        "rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin",
        "nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin",
        "sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin",
        "dbus:x:81:81:System message bus:/:/sbin/nologin",
        "ec2-user:x:500:500:EC2 Default User:/home/ec2-user:/bin/bash",
        "slicer:x:498:497::/tmp:/sbin/nologin",
        "sb_logger:x:497:496::/tmp:/sbin/nologin",
        "sbx_user1051:x:496:495::/home/sbx_user1051:/sbin/nologin",
        "sbx_user1052:x:495:494::/home/sbx_user1052:/sbin/nologin",
        "sbx_user1053:x:494:493::/home/sbx_user1053:/sbin/nologin",
        "sbx_user1054:x:493:492::/home/sbx_user1054:/sbin/nologin",
        "sbx_user1055:x:492:491::/home/sbx_user1055:/sbin/nologin",
        "sbx_user1056:x:491:490::/home/sbx_user1056:/sbin/nologin",
        "sbx_user1057:x:490:489::/home/sbx_user1057:/sbin/nologin",
        "sbx_user1058:x:489:488::/home/sbx_user1058:/sbin/nologin",
        "sbx_user1059:x:488:487::/home/sbx_user1059:/sbin/nologin",
        "sbx_user1060:x:487:486::/home/sbx_user1060:/sbin/nologin",
        "sbx_user1061:x:486:485::/home/sbx_user1061:/sbin/nologin",
        "sbx_user1062:x:485:484::/home/sbx_user1062:/sbin/nologin",
        "sbx_user1063:x:484:483::/home/sbx_user1063:/sbin/nologin",
        "sbx_user1064:x:483:482::/home/sbx_user1064:/sbin/nologin",
        "sbx_user1065:x:482:481::/home/sbx_user1065:/sbin/nologin",
        "sbx_user1066:x:481:480::/home/sbx_user1066:/sbin/nologin",
        "sbx_user1067:x:480:479::/home/sbx_user1067:/sbin/nologin",
        "sbx_user1068:x:479:478::/home/sbx_user1068:/sbin/nologin",
        "sbx_user1069:x:478:477::/home/sbx_user1069:/sbin/nologin",
        "sbx_user1070:x:477:476::/home/sbx_user1070:/sbin/nologin",
        "sbx_user1071:x:476:475::/home/sbx_user1071:/sbin/nologin",
        "sbx_user1072:x:475:474::/home/sbx_user1072:/sbin/nologin",
        "sbx_user1073:x:474:473::/home/sbx_user1073:/sbin/nologin",
        "sbx_user1074:x:473:472::/home/sbx_user1074:/sbin/nologin",
        "sbx_user1075:x:472:471::/home/sbx_user1075:/sbin/nologin",
        "sbx_user1076:x:471:470::/home/sbx_user1076:/sbin/nologin",
        "sbx_user1077:x:470:469::/home/sbx_user1077:/sbin/nologin",
        "sbx_user1078:x:469:468::/home/sbx_user1078:/sbin/nologin",
        "sbx_user1079:x:468:467::/home/sbx_user1079:/sbin/nologin",
        "sbx_user1080:x:467:466::/home/sbx_user1080:/sbin/nologin",
        "sbx_user1081:x:466:465::/home/sbx_user1081:/sbin/nologin",
        "sbx_user1082:x:465:464::/home/sbx_user1082:/sbin/nologin",
        "sbx_user1083:x:464:463::/home/sbx_user1083:/sbin/nologin",
        "sbx_user1084:x:463:462::/home/sbx_user1084:/sbin/nologin",
        "sbx_user1085:x:462:461::/home/sbx_user1085:/sbin/nologin",
        "sbx_user1086:x:461:460::/home/sbx_user1086:/sbin/nologin",
        "sbx_user1087:x:460:459::/home/sbx_user1087:/sbin/nologin",
        "sbx_user1088:x:459:458::/home/sbx_user1088:/sbin/nologin",
        "sbx_user1089:x:458:457::/home/sbx_user1089:/sbin/nologin",
        "sbx_user1090:x:457:456::/home/sbx_user1090:/sbin/nologin",
        "sbx_user1091:x:456:455::/home/sbx_user1091:/sbin/nologin",
        "sbx_user1092:x:455:454::/home/sbx_user1092:/sbin/nologin",
        "sbx_user1093:x:454:453::/home/sbx_user1093:/sbin/nologin",
        "sbx_user1094:x:453:452::/home/sbx_user1094:/sbin/nologin",
        "sbx_user1095:x:452:451::/home/sbx_user1095:/sbin/nologin",
        "sbx_user1096:x:451:450::/home/sbx_user1096:/sbin/nologin",
        "sbx_user1097:x:450:449::/home/sbx_user1097:/sbin/nologin",
        "sbx_user1098:x:449:448::/home/sbx_user1098:/sbin/nologin",
        "sbx_user1099:x:448:447::/home/sbx_user1099:/sbin/nologin",
        "sbx_user1100:x:447:446::/home/sbx_user1100:/sbin/nologin",
        "sbx_user1101:x:446:445::/home/sbx_user1101:/sbin/nologin",
        "sbx_user1102:x:445:444::/home/sbx_user1102:/sbin/nologin",
        "sbx_user1103:x:444:443::/home/sbx_user1103:/sbin/nologin",
        "sbx_user1104:x:443:442::/home/sbx_user1104:/sbin/nologin",
        "sbx_user1105:x:442:441::/home/sbx_user1105:/sbin/nologin",
        "sbx_user1106:x:441:440::/home/sbx_user1106:/sbin/nologin",
        "sbx_user1107:x:440:439::/home/sbx_user1107:/sbin/nologin",
        "sbx_user1108:x:439:438::/home/sbx_user1108:/sbin/nologin",
        "sbx_user1109:x:438:437::/home/sbx_user1109:/sbin/nologin",
        "sbx_user1110:x:437:436::/home/sbx_user1110:/sbin/nologin",
        "sbx_user1111:x:436:435::/home/sbx_user1111:/sbin/nologin",
        "sbx_user1112:x:435:434::/home/sbx_user1112:/sbin/nologin",
        "sbx_user1113:x:434:433::/home/sbx_user1113:/sbin/nologin",
        "sbx_user1114:x:433:432::/home/sbx_user1114:/sbin/nologin",
        "sbx_user1115:x:432:431::/home/sbx_user1115:/sbin/nologin",
        "sbx_user1116:x:431:430::/home/sbx_user1116:/sbin/nologin",
        "sbx_user1117:x:430:429::/home/sbx_user1117:/sbin/nologin",
        "sbx_user1118:x:429:428::/home/sbx_user1118:/sbin/nologin",
        "sbx_user1119:x:428:427::/home/sbx_user1119:/sbin/nologin",
        "sbx_user1120:x:427:426::/home/sbx_user1120:/sbin/nologin",
        "sbx_user1121:x:426:425::/home/sbx_user1121:/sbin/nologin",
        "sbx_user1122:x:425:424::/home/sbx_user1122:/sbin/nologin",
        "sbx_user1123:x:424:423::/home/sbx_user1123:/sbin/nologin",
        "sbx_user1124:x:423:422::/home/sbx_user1124:/sbin/nologin",
        "sbx_user1125:x:422:421::/home/sbx_user1125:/sbin/nologin",
        "sbx_user1126:x:421:420::/home/sbx_user1126:/sbin/nologin",
        "sbx_user1127:x:420:419::/home/sbx_user1127:/sbin/nologin",
        "sbx_user1128:x:419:418::/home/sbx_user1128:/sbin/nologin",
        "sbx_user1129:x:418:417::/home/sbx_user1129:/sbin/nologin",
        "sbx_user1130:x:417:416::/home/sbx_user1130:/sbin/nologin",
        "sbx_user1131:x:416:415::/home/sbx_user1131:/sbin/nologin",
        "sbx_user1132:x:415:414::/home/sbx_user1132:/sbin/nologin",
        "sbx_user1133:x:414:413::/home/sbx_user1133:/sbin/nologin",
        "sbx_user1134:x:413:412::/home/sbx_user1134:/sbin/nologin",
        "sbx_user1135:x:412:411::/home/sbx_user1135:/sbin/nologin",
        "sbx_user1136:x:411:410::/home/sbx_user1136:/sbin/nologin",
        "sbx_user1137:x:410:409::/home/sbx_user1137:/sbin/nologin",
        "sbx_user1138:x:409:408::/home/sbx_user1138:/sbin/nologin",
        "sbx_user1139:x:408:407::/home/sbx_user1139:/sbin/nologin",
        "sbx_user1140:x:407:406::/home/sbx_user1140:/sbin/nologin",
        "sbx_user1141:x:406:405::/home/sbx_user1141:/sbin/nologin",
        "sbx_user1142:x:405:404::/home/sbx_user1142:/sbin/nologin",
        "sbx_user1143:x:404:403::/home/sbx_user1143:/sbin/nologin",
        "sbx_user1144:x:403:402::/home/sbx_user1144:/sbin/nologin",
        "sbx_user1145:x:402:401::/home/sbx_user1145:/sbin/nologin",
        "sbx_user1146:x:401:400::/home/sbx_user1146:/sbin/nologin",
        "sbx_user1147:x:400:399::/home/sbx_user1147:/sbin/nologin",
        "sbx_user1148:x:399:398::/home/sbx_user1148:/sbin/nologin",
        "sbx_user1149:x:398:397::/home/sbx_user1149:/sbin/nologin",
        "sbx_user1150:x:397:396::/home/sbx_user1150:/sbin/nologin",
        "sbx_user1151:x:396:395::/home/sbx_user1151:/sbin/nologin",
        "sbx_user1152:x:395:394::/home/sbx_user1152:/sbin/nologin",
        "sbx_user1153:x:394:393::/home/sbx_user1153:/sbin/nologin",
        "sbx_user1154:x:393:392::/home/sbx_user1154:/sbin/nologin",
        "sbx_user1155:x:392:391::/home/sbx_user1155:/sbin/nologin",
        "sbx_user1156:x:391:390::/home/sbx_user1156:/sbin/nologin",
        "sbx_user1157:x:390:389::/home/sbx_user1157:/sbin/nologin",
        "sbx_user1158:x:389:388::/home/sbx_user1158:/sbin/nologin",
        "sbx_user1159:x:388:387::/home/sbx_user1159:/sbin/nologin",
        "sbx_user1160:x:387:386::/home/sbx_user1160:/sbin/nologin",
        "sbx_user1161:x:386:385::/home/sbx_user1161:/sbin/nologin",
        "sbx_user1162:x:385:384::/home/sbx_user1162:/sbin/nologin",
        "sbx_user1163:x:384:383::/home/sbx_user1163:/sbin/nologin",
        "sbx_user1164:x:383:382::/home/sbx_user1164:/sbin/nologin",
        "sbx_user1165:x:382:381::/home/sbx_user1165:/sbin/nologin",
        "sbx_user1166:x:381:380::/home/sbx_user1166:/sbin/nologin",
        "sbx_user1167:x:380:379::/home/sbx_user1167:/sbin/nologin",
        "sbx_user1168:x:379:378::/home/sbx_user1168:/sbin/nologin",
        "sbx_user1169:x:378:377::/home/sbx_user1169:/sbin/nologin",
        "sbx_user1170:x:377:376::/home/sbx_user1170:/sbin/nologin",
        "sbx_user1171:x:376:375::/home/sbx_user1171:/sbin/nologin",
        "sbx_user1172:x:375:374::/home/sbx_user1172:/sbin/nologin",
        "sbx_user1173:x:374:373::/home/sbx_user1173:/sbin/nologin",
        "sbx_user1174:x:373:372::/home/sbx_user1174:/sbin/nologin",
        "sbx_user1175:x:372:371::/home/sbx_user1175:/sbin/nologin",
        "sbx_user1176:x:371:370::/home/sbx_user1176:/sbin/nologin"
    ],
    "ulimit -a": [
        "core file size          (blocks, -c) unlimited",
        "data seg size           (kbytes, -d) unlimited",
        "scheduling priority             (-e) 0",
        "file size               (blocks, -f) unlimited",
        "pending signals                 (-i) 14992",
        "max locked memory       (kbytes, -l) 64",
        "max memory size         (kbytes, -m) unlimited",
        "open files                      (-n) 1024",
        "pipe size            (512 bytes, -p) 8",
        "POSIX message queues     (bytes, -q) 819200",
        "real-time priority              (-r) 0",
        "stack size              (kbytes, -s) 8192",
        "cpu time               (seconds, -t) unlimited",
        "max user processes              (-u) 1024",
        "virtual memory          (kbytes, -v) unlimited",
        "file locks                      (-x) unlimited"
    ],
    "/sbin/ip a": [
        "1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1",
        "    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00",
        "    inet 127.0.0.1/8 scope host lo",
        "       valid_lft forever preferred_lft forever",
        "33: vinternal_11@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000",
        "    link/ether ca:6e:10:ca:95:3c brd ff:ff:ff:ff:ff:ff link-netnsid 0",
        "    inet 169.254.76.21/23 scope global vinternal_11",
        "       valid_lft forever preferred_lft forever",
        "36: vtarget_6@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000",
        "    link/ether 0e:97:c0:54:7b:9d brd ff:ff:ff:ff:ff:ff link-netnsid 1",
        "    inet 169.254.79.1/32 scope global vtarget_6",
        "       valid_lft forever preferred_lft forever"
    ],
    "/sbin/ip r": [
        "default via 169.254.76.22 dev vinternal_11 ",
        "169.254.76.0/23 dev vinternal_11  proto kernel  scope link  src 169.254.76.21 ",
        "169.254.76.22 dev vinternal_11  scope link ",
        "169.254.79.2 dev vtarget_6  scope link "
    ],
    "netstat -av": [
        "netstat: no support for `AF INET (sctp)' on this system.",
        "netstat: no support for `AF INET (sctp)' on this system.",
        "netstat: no support for `AF IPX' on this system.",
        "netstat: no support for `AF AX25' on this system.",
        "netstat: no support for `AF X25' on this system.",
        "netstat: no support for `AF NETROM' on this system.",
        "Active Internet connections (servers and established)",
        "Proto Recv-Q Send-Q Local Address               Foreign Address             State      ",
        "udp        0      0 169.254.79.1:58860          169.254.79.2:sieve-filter   ESTABLISHED ",
        "Active UNIX domain sockets (servers and established)",
        "Proto RefCnt Flags       Type       State         I-Node Path"
    ]
}

雑感

NW周りが興味深いですね!

env の結果で以下のようなものがあるんですが、そこだけルーティングが別になっていたり。ちなみに同じ 169.254 から始まるEC2のmetadataを取る 169.254.169.254IPアドレスへの接続は通らないようになってるんですよね。

        "_AWS_XRAY_DAEMON_ADDRESS=169.254.79.2",
        "_AWS_XRAY_DAEMON_PORT=2000",
    "/sbin/ip r": [
        "default via 169.254.76.22 dev vinternal_11 ",
        "169.254.76.0/23 dev vinternal_11  proto kernel  scope link  src 169.254.76.21 ",
        "169.254.76.22 dev vinternal_11  scope link ",
        "169.254.79.2 dev vtarget_6  scope link "
    ],

netstat -a で得られる接続先がこれしかなかったり、 sieve-filter はググったら Dovecot と組み合わせて使うメールフィルタのようです。

udp        0      0 169.254.79.1:58860          169.254.79.2:sieve-filter   ESTABLISHED 

あとは、 df -a で見られるファイルシステムがDockerコンテナより圧倒的に少ないなーとか(Dockerも起動方法次第なのかもですが)、Lambdaの最大メモリが3GBになって、2vCPUあたるようになったからかそれに合わせたVMに載ってるなーとか、プロセスの起動ユーザが sbx_userXXXX なのは知っていたのですが、なんか一杯作ってあるなーとか、仕事に役立つかは不明ですが色々興味深くはあります。

追試:Lambdaのインスタンスガチャを検証する

さて、ではみんな気になる?Lambdaのインスタンスガチャを検証してみます。

serverless.yml でLambdaのメモリ割り当てを増やしてインスタンスが別れるように仕向けます。

provider:
  name: aws
  runtime: python3.6
  memorySize: 3008

そして、こんなコードをデプロイ。

import subprocess
import time


def osdata(event, context):
    time.sleep(1)
    return subprocess.getoutput('cat /proc/cpuinfo | grep "model name" | uniq')

こんな感じの手抜きワンライナーで実行します。

for i in $(seq 1 10); do sls invoke -f osdata >> result.txt & done; wait; cat result.txt | sort | uniq -c

結果②

   8 "model name\t: Intel(R) Xeon(R) CPU E5-2666 v3 @ 2.90GHz"
   2 "model name\t: Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz"

ガチャだ・・・!(とはいえ、どちらも良いCPUですが) Lambdaの場合はコンテナなので cgroups あたりで調整してたり・・・はしないかなw

世代的にはC系の最新から1〜2世代前って感じですかね。極端に古くはなくて安心しましたw

ちなみに本検証はServerless Frameworkがデフォルトで利用する us-east-1 で検証してます。

パリみたいに最初からC5系しか居ないリージョンなら、割り当てリソースが同じでもファンクションの単体性能は変わりそうですね

こんなコメントも貰ったので、リージョンによってまた結果は変わりそうですね。

こちらからは以上です

元々書く予定だったのは近いうちに書くはず!(と言って去年は書かなかったことがあったけど今年は絶対書くつもり)

Alexa Skillの開発をServerless Frameworkだけで完結するための「Serverless Alexa Skills Plugin」の紹介

この記事は、Serverless Advent Calendar 13日目の記事です。

qiita.com

今回は拙作ですがオススメのServerless FrameworkのPluginを紹介したいと思います。

概要

Serverless Alexa Skills Pluginは、Serverless FrameworkでAlexa Skill用のLambda Functionを開発しながら、スキル周辺の設定も serverless.yml および serverless(sls) コマンドから管理できるようにすることで、Alexa Skillの開発をServerless Frameworkで統合管理するためのPluginです。

github.com

経緯など

Alexa Skillの開発にはLambda Functionの開発がセット

Alexaのカスタムスキルを作る場合、基本的にはLambda Functionの開発がセットになります。もちろん、Lambdaを使わずに特定のHTTP Endpointとやり取りすることで実行することもできますが、HTTPサーバを作って運用したりする手間とコストを考えるとLambdaを使う方が大抵の場合楽ですし、コストメリットも高いです。

developer.amazon.com

Lambda Functionの開発はServerless Frameworkでやりたい

最近は専らLambda Functionの開発はServerless Frameworkでやっています。以前は独自のデプロイツールを作って使っていたりもしましたが、v1.0からFrameworkの内部構造がそれ以前より段違いで良くなり、足りない機能はpull requestを送ったりpluginを作ったりして補えるようになったため、フルタイムで会社まで作ってメンテしていて、良い感じにエコシステムが確立しつつある製品に対抗してもしょうがないので乗っかる方針でやっています。

Alexa Skillの設定管理がめんどくさい

対して、Alexa Skillの設定はAlexa Skill Kitのコンソール画面から行うか、ack-cliを使う方法が公式で提唱されています。このack-cliがまた設定をJSONで書かないといけなかったり、公式ツールなのでしょうがないのですが、APIに忠実に作られているので必要なコマンド数が多かったりするわけです。

developer.amazon.com

Serverless Frameworkで全部良い感じにやりたい

そんな思いから生まれたのがServerless Alexa Skills Pluginというわけです。

使い方

インストール

インストールはnpmにリリースされてるのでこれだけです。

$ npm install -g serverless
$ serverless plugin install -n serverless-alexa-skills

認証情報の取得

利用するためにはLambda FunctionをデプロイするためのAWSの認証情報もそうですが、Alexa Skills Kit APIを実行するための認証情報も必要になります。AlexaはAWSではなくAmazon本体の管轄であり、Login with Amazon というAmazon本体アカウントを使ったOAuth2.0によるシングルサインオンで認証を受ける必要が有るため、認証情報の扱いが異なるのでそこを説明したいと思います。AWSの認証情報の取得方法についてはここでは触れません。

まず、Amazon Developer Consoleへログインします。

developer.amazon.com

APPS&SERVICES タブから Login with Amazon へ移動し、 Create a New Security Profile からセキュリティプロファイルを作成します。

f:id:FumblePerson:20171213225925p:plain

各入力項目は適当で良いです。

f:id:FumblePerson:20171213230336p:plain

作成したら、出来上がったセキュリティプロファイルの Web Settings を設定します。

f:id:FumblePerson:20171213230629p:plain

Allowed Origins は空で良いです。 Allowed Return URLshttp://localhost:3000 を入力します。このポート番号は設定(serverless.yml)で変更可能なので、変えたい場合は変えても良いです。

f:id:FumblePerson:20171213230810p:plain

ここまでできたら、できあがったセキュリティプロファイルの Client IDClient Secret を控えておきましょう。併せてもう一つ、 Vendor ID というものも必要になるので、Developer Consoleへログインした状態でコチラへアクセスして控えておきましょう。

ちょっと面倒ですが、この作業は初回だけです。同じアカウントを使う限りは今後いくつスキルを作ろうと同じ情報が使えます(漏洩などして入れ替えが必要にならなければw)

面倒な画面ポチポチはここまでです!!ここから先はみんな大好きの黒い画面をメインに進みます!!

認証情報をセット

取得した認証情報を serverless.yml へ書き込みます。直接書き込むのに抵抗がある場合は下記のように環境変数を利用すると良いでしょう。また、 Allowed Return URLs でポート番号を買えた場合は localServerPort という設定を追加してポート番号を書き込んでください。

provider:
  name: aws
  runtime: nodejs6.10

plugins:
  - serverless-alexa-skills

custom:
  alexa:
    vendorId: ${env:YOUR_AMAZON_VENDOR_ID}
    clientId: ${env:YOUR_AMAZON_CLIENT_ID}
    clientSecret: ${env:YOUR_AMAZON_CLIENT_SECRET}

そして、以下のコマンドを実行します。

$ serverless alexa auth

このコマンドを実行するとブラウザが開かれAmazonへのログイン画面が出てきます。そこでログインに成功すると localhost:3000 へリダイレクトされ、認証に成功していれば Thank you for using Serverless Alexa Skills Plugin!! とデザイン皆無の味も素っ気もないメッセージが表示されているはずです!w

ちなみにこれによって取得した認証トークンの有効期限は1時間となっています。今後、トークンの自動リフレッシュなど実装予定ですが、現状では認証エラーが出たら再度上記コマンドの実行をお願いします。

スキルの作成

それではスキルを作ってみましょう。以下のコマンドを実行します。

$ serverless alexa create --name $YOUR_SKILL_NAME --locale $YOUR_SKILL_LOCALE --type $YOUR_SKILL_TYPE

それぞれオプションは以下の通りです。

  • name(n): スキルの名前
  • locale(l): スキルのロケール。日本語なら ja-JP, 英語なら en-US
  • type(t): スキルのタイプ。 custom or smartHome or video

スキルマニフェストの更新

スキルを作ったら、スキルの基本的な設定を表すスキルマニフェストを設定する必要があります。今作成したスキルに初期設定されているマニフェストは以下のコマンドで確認できます。

$ severless alexa manifests

Serverless: 
----------------
[Skill ID] amzn1.ask.skill.xxxxxx-xxxxxx-xxxxx
[Stage] development
[Skill Manifest]
skillManifest:
  publishingInformation:
    locales:
      ja-JP:
        name: sample
  apis:
    custom: {}
  manifestVersion: '1.0'

実行すると上記のような出力が出るので、 [Skill ID][Skill Manifest] をコピーして serverless.yml へ以下のように貼り付けます。

custom:
  alexa:
    vendorId: ${env:AMAZON_VENDOR_ID}
    clientId: ${env:AMAZON_CLIENT_ID}
    clientSecret: ${env:AMAZON_CLIENT_SECRET}
    skills:
      - id: ${env:ALEXA_SKILL_ID}
        skillManifest:
          publishingInformation:
            locales:
              ja-JP:
                name: sample
          apis:
            custom: {}
          manifestVersion: '1.0'

この serverless.yml の設定を更新後、下記のコマンドを実行するとマニフェストが更新されます。ちなみに恒例?の --dryRun/-d オプションで実際に更新は行わずに更新内容だけ確認できます。

$ serverless alexa update

マニフェストの設定構文についてはAPIに準じているので、こちらで確認できます。

developer.amazon.com

対話モデルの構築

Alexaスキルを動かすためには、対話モデルの構築が必要です。これもマニフェストとほぼ同じ流れでできますが、作成しただけのスキルは対話モデルを持っていないのでまず先に serverless.yml に対話モデルの設定を書き込む所から始まります。

custom:
  alexa:
    vendorId: ${env:AMAZON_VENDOR_ID}
    clientId: ${env:AMAZON_CLIENT_ID}
    clientSecret: ${env:AMAZON_CLIENT_SECRET}
    skills:
      - id: ${env:ALEXA_SKILL_ID}
        skillManifest:
          publishingInformation:
            locales:
              ja-JP:
                name: sample
          apis:
            custom: {}
          manifestVersion: '1.0'
        models:
          ja-JP:
            interactionModel:
              languageModel:
                invocationName: PPAP
                intents:
                  - name: PineAppleIntent
                    slots:
                    - name: Fisrt
                      type: AMAZON.Food
                    - name: Second
                      type: AMAZON.Food

このような形で models.ロケール.interactionModel という構造で下記に準じた対話モデルを設定します。

developer.amazon.com

そして、下記のコマンドで対話モデルを更新し、構築します。ここでも --dryRun が利用可能です。

$ serverless alexa build

構築した対話モデルは下記のコマンドで確認可能です。

$ serverless alexa models

Serverless: 
-------------------
[Skill ID] amzn1.ask.skill.xxxx-xxxx-xxxxx
[Locale] ja-JP
[Interaction Model]
interactionModel:
  languageModel:
    invocationName: PPAP
    intents:
      - name: PineAppleIntent
        slots:
        - name: Fisrt
          type: AMAZON.Food
        - name: Second
          type: AMAZON.Food

現段階では以上です

ここから実際にスキルを公開するまではもう少しステップがありますが、一番変更が多く試行錯誤が必要でバージョン管理しておきたいマニフェストと対話モデルが統合管理できるのは大きいのではないかと思います。今後Alexa Skills Kitを通したテストや公開まで統合できるよう開発は進めていきたいと思っています。

また、ここでは触れなかったServerless FrameworkでAlexa Skill用のLambda Functionを実装する方法についてはこちらをご確認ください。

serverless.com

良かったら今後Alexaスキルを作る際にご利用いただき、フィードバックやStarをいただけると励みになりますので、よろしくお願いします。

github.com

2017年もServerlessConf Tokyoは最高だった #serverlessconf

去年に引き続き今年もスピーカーとして参加させてもらいました。

marcy.hatenablog.com

会社はスポンサーもやってるんですが、セッションとLTの両方にプロポーザルが通ってしまって余裕がないので同僚に基本任せっぱなしでした(ゴメンナサイ)

会場の雰囲気など

アンダーグラウンドな雰囲気がカッコよかった去年に比べて、今年は立派なカンファレンスになった感じでした。ですが、ベンダーフリーで自由な主張が許される雰囲気は変わっておらず、正当な進化を遂げたという印象でした。去年は食事と最後のソーシャルタイム?のお酒が凄い充実していたけど、代わりに今年はノベルティのTシャツが超カッコ良いので満足しています。

ソーシャルタイムは他の登壇者や普段会えない人達と話せる良い時間だったのですが、それが無くなったのはちょっと残念です。けど、規模的にも時間的にもまあ難しいだろうなという所なので仕方ないですね。個人的にはアツいメンバーと終了後に飲みに行けて良い話を一杯できたので救われたんですけど、もしそれが無かったとしたらちょっと物足りなかったかもしれないなと。

セッション

slideship.com

こちらが今回のメインです。「2年目なので今年はツールとかサービスの外側の話は一切封印して、より具体的な中身の設計とか実装の話をしたい」というのだけはもう初めから決めていて、事例をベースにしながら話すパターンと、大きなテーマだけ掲げてあとはとにかく設計と実装の話をし倒すパターンの2種類のプロポーザルを出した結果、後者を採用していただけました。そして、念のためと思って出したLTも一緒に通ってしまったというw

セッションの冒頭でも話したのですが、去年超有名エンジニアの方にパネルでボコボコにされたのが悔しくて、その時答えられなかったあらゆる質問の答えを持ってきたつもりです。Serverlessでも信頼性の求められるシステムは作れる。(もちろん、その方のことは全く恨んでいません。むしろ去年の自分が如何に甘かったか痛感させてくれてとても感謝してます)

LT

slideship.com

煽り芸に初挑戦してみました。会場でもその後の反応も良くて嬉しかったです。ただ一点ミスがあって「リレーション」は正しくは「リレーションシップ」ですね。普段から理解はしているものの略語みたいな感覚で誤用してしまっているので、名前は正しく表現すべきエンジニアにあるまじきミスなので、気をつけたいと思います。。。

他の方々のセッション

いくつか印象的だったものを(大体時系列順のつもり)

サーバーレスについて語るときに僕の語ること

speakerdeck.com

一応スポンサー企業所属なので、ブースはできなくてもランチスポンサーセッションを放っていくのはどうかってことで、現場では聞けなかったのが残念でした。ですが、基本的には全面的にAgreeです。イベントドリブンこそ本質で、そこの議論をもっと深めて行きたいというのはずっと思っていて「よくぞ言ってくれた!!」という気持ちです。

marcy.hatenablog.com

ただ、Serverlessの良いところの一つが文脈が広く寛容的である所だとも思っていて、入り口としての意味も込めて今までの文脈も残しておいても良いのかなと。隔絶しちゃわない程度に上手く棲み分けしていきたいですね。

アンケートにも書いたのですが、私にとってServerlessとは定義としてはイベントドリブンな文脈が正しいと思っているものの、Serverlessという言葉自体は「ベンダーの垣根を越えて真のクラウドネイティブアーキテクチャを議論するためのネームスペース」って思ったりしてます。そういうコミュニティに成長していってほしいと思うし、微力ながらお手伝いしたいです。

真のサーバレスアーキテクトとサーバレス時代のゲーム開発・運用

speakerdeck.com

おそらく日本のサービス(プラットフォーム)開発者の中で一番本気でServerlessをやっているであろう丹羽さん。さすがの圧倒的知見ですね。立場は違えど、要所要所で同じ答えに辿り着いている感じがして勝手に答え合わせしてもらった気持ちです。現場で聞きたかった。裏番組だったのが本当に悔やまれますw

Open source application and Ecosystem on Serverless Framework

speakerdeck.com

福岡のサーバレスおじさん、さすがの実装力です。いつもawspecのコンセプトデザイナーだと私のことを立ててくれるんですが、私はたまたま先に同じことを思いついて途中で投げ出しただけ(ちょうどaws-sdkのメジャーバージョンアップと重なって一旦凍結したという言い訳はあるけど、その後再開しなかったのでホントただの言い訳)なのです。今回も圧倒的実装力で楽しい世界を見せてくれました。自分ももっとコード書かねばと思わせてくれました。

OpenSource with Serverless World

speakerdeck.com

日本人初どころか、Serverless Inc.社外では初のメンテナの座を日々の努力で勝ち取ったアツい気持ちは本当に脱帽します。

Serverless Frameworkについては1.0が出たタイミングでそれ以前から比べると圧倒的に洗練された内部構造になったことに関心していて、その後もLamveryにあってServerless Frameworkに無い機能の中でどうしても欲しいものはPRを出したりプラグインで解決できるようになったので、日本人コミッター(しかも知り合い)が居る安心感も合わせてもうデプロイの所はServerless Frameworkに任せたい気持ちです。

その他、現状資料未公開(と思われる)もの

A Cloud Guruの人とAWSのServerless系サービスのProduct Managerが話していたServerless Architecture Patternは超実戦的で必見な感じでした。資料公開に期待したい。Googleの浦底さんもLT準備でチラチラ覗くことしかできなかったので、趣味でGCPをやっている身からは資料公開に期待したい(けど、始まる前に個人の主張であることを強調してたので難しいのかな)

あと、nekoruriさんの「アウトプットしないのは知的な便秘」は的確すぎて刺さりました。慢性的にちょっとずつ出しても便秘は解消しないですもんねw栄養の吸収も悪くなるしwほんと的確過ぎるwwwスッキリ出すの大事。

おまけの与太話

ワークショップは今年は参加できなかった。。。PyCon JPもそうだったんですが、チケットが早く売り切れてしまうと地方勢としてはちょっとツラい所があります。飛行機やホテルがあって行けることが確定できないと色々無駄になってしまう可能性がある・基本泊まりになるのでスケジュールの調整難度が高いので、理想を言うと2週間前くらいまではジワジワ追加販売とかしてもらえると嬉しいです。>カンファレンス主催者各位

個人的には行きたいカンファレンスはCFPも出す率が高くて(通れば無理を通してでも100%行くので)CFP落選後に落ちても行きたいか考えてから買えるタイミングがあると嬉しいけど、これはかなり特殊な例だと思うので配慮しなくて良いですwServerlessConfについては落ちてもほぼ間違いなく行ってただろうから買っておけば良かったんですけどね。怠慢。。。

でもまあ、圧倒的少数派に配慮して多数派の満足度を損なうのもどうかって話もあると思うんで、特に文句があるわけじゃなくただの願望です。地方でMeetupやった時に大盛り上がりとかしてれば自然と配慮してもらえるだろうし、コチラの問題ではって気もします。

(カンファレンスで数百人を沸かせることができても、地元では20人集めるのにも苦労してるので悲しくなる)

最後に

スピーカーで2年連続はクラウドベンダー勢を除くと私と丹羽さんだけのようですね。丹羽さんはさっきも言ったようにサービス開発者の中では一番だと思っているので、自分はインテグレーターの中では一番だという気持ちで、来年もあの場に立たせてもらえるように一年間精進していきたいと思います。

主催の吉田さん、スタッフの皆様、今年も最高のカンファレンスをありがとうございました!!

DynamoDBをキャッシュストアっぽく使うPyPIライブラリを作りました

仕事は一応納まったけど、リモートワークなので忘年会とかは特に無くて、奥さんも体調が良くないので年末年始に備えて寝てしまったので、一人寂しくブログ書いてます(;´Д`)

何はともあれソースなど

github.com

pypi.python.org

背景や動機

Lambdaでアプリケーションを作っていると、外部のSaaSAPIを叩いたりする時にrate limitやレイテンシが気になったり、DynamoDBに重めのクエリを投げると性能だったりキャパシティが気になって結果をキャッシュしておきたかったり、キャッシュストアが欲しくなることがままあります。

なんですが、ElastiCacheはVPC内で使うとENI生成時の遅延が気になるし、せっかく楽がしたくてServerlessなのにマネージドとはいえインスタンスを気にしないといけないのも微妙なところです。となると、Lambdaから手頃に使えるデータストアは結局DynamoDBしかないわけです。(AppEngineならMemcacheがあるから、もうAppEngineで良いんじゃないか?とか思ってしまうw)

じゃあ、もうDynamoDBをキャッシュストアの代わりに使うしかないよねってことで、それっぽいライブラリを探したんですが、見つからなかったので作りました。

インストール

Pythonおよびpipのインストールは省略すると、PyPIなので、これだけです。

pip install ddbc

余談ですが、APIが変わったらしく、PyPIのパッケージのアップロード方法がいつの間にか変わってたんですね。前からあるやつは手元のvirtualenv環境が古いままだからか気が付かなった。。。

準備

IAM権限

以下のようなPolicyを持つIAM Role/Userを使用します。

<cache-table> の所は実際に使用するキャッシュ用のDynamoDBテーブル名が入ります。
<region>:<account-id> の所はお使いの環境に合わせてください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
              "dynamodb:CreateTable",
              "dynamodb:DeleteItem",
              "dynamodb:GetItem",
              "dynamodb:PutItem",
              "dynamodb:DescribeTable"
            ],
            "Resource": "arn:aws:dynamodb:<region>:<account-id>:table/<cache-table>"
        }
    ]
}

キャッシュ用のテーブルを作成

上記の権限を割り当てた環境で以下のようなコードを実行します。既に存在する場合は何もしないので、2回以上実行してしまっても無問題です。

#!/usr/bin/env python

import ddbc.utils

ddbc.utils.create_table(
    table_name='cache_table',
    region='us-east-1', # optional
    read_units=10,      # default: 5
    write_units=10      # default: 5
)

使用方法

キャッシュクライアント生成

import ddbc.cache
import time

cache = ddbc.cache.Client(
    table_name='cache_table',
    region='us-east-1', # optional
    default_ttl=100,    # default: -1 (Infinity)
    report_error=True   # default: False
)

default_ttl がキャッシュデータをセットする際にデフォルトで適用するデータのTTLです。未指定時は無限になります。
report_error はDynamoDB APIのコール時に発生した例外を報告(再スロー)するかどうかです。キャッシュストアなので、一時的なエラーやスロットリングなどの想定されるエラーは無視しても良い場面が多いので、未指定時は例外は握りつぶし、読み込み時は失敗時に None (変更可能)、書き込み系の操作なら結果を True (成功) / False (失敗) で返します。

キャッシュデータ操作

ざっとこんな感じです。pylibmc をなんとなく意識した感じで dict っぽく使えます。

cache['foo'] = 'bar'
print(cache['foo']) # => 'bar'

time.sleep(100)
print(cache['foo']) # => None

cache.set('foo', 'bar', 1000)
time.sleep(100)
print(cache['foo']) # => 'bar'

del cache['foo']
print(cache.get('foo')) # => None
print(cache.get('foo', 'buz')) # => 'buz'

補足

ちなみに、DynamoDBへのリクエストも減らすためにメモリ上にもキャッシュを持ちます。書き込みは毎回リクエストが発生しますが、読み込みはメモリ上のキャッシュが有効な間はそこからデータを取得します。なので、Lambdaもコンテナの再利用がされている場合はメモリにキャッシュ済みの有効期限内のデータを使うケースではDynamoDBへのリクエストは飛びません。

また、キー範囲が偏ると性能が出ないDynamoDBに配慮して一応キーはハッシュ化して分散するようにしています。

現状、有効期限が切れたデータは削除する際のスループット消費の方が気になるので、消されずにそのまま放置されます。

あと、1つのキーに対するデータは pickleシリアライズした文字列長でDynamoDBの制限である400KBまでしか入りません。

docs.aws.amazon.com

最後に

Lambda上でのオンライン処理で使えるデータストアが実質DynamoDBくらいしかない現状では、それなりに便利かと思うので良かったら使ってみてください。

AWSさんには、Serverlessなキャッシュストア的な新サービスと、SimpleDBの再興を切に願うばかりです。来年は是非おなしゃす。

それでは、良いお年を。

AppEngineで作るComputeEngineの定期バックアップシステム

この記事は、Google Cloud Platform Advent Calenderの21日目の記事です。遅れに遅れてしまい大変申し訳ありません。。。

エントリー時は「Cloud Functionsでなにか」と書いていたのですが、イマイチ目新しい機能も出ておらず良いネタがでなかったので、過去にAppEngineで作ったComputeEngineの定期バックアップシステムの紹介をしたいと思います。

前置きと概要

AWS方面ではわりと早い段階からAPIとタグを駆使したEC2の定期バックアップ手法は有名でした。EC2にバックアップ世代数のタグを設定し、そのタグがついているインスタンスを対象にバックアップを行うバッチを定期で起動します。

blog.suz-lab.com

最近だと、CloudWatch Eventsを利用してEC2要らずで行うことができます。

qiita.com

GCEでの例を見かけなかったのですが、基本的にはGCPスケールメリットを活かすにはステートレスなアプリケーションが前提となるので、そうするとデータを基本的にマネージドなデータストアに入れておけば良い話なので、そういったことは必要ないんだろうなと。

ただ、東京リージョンもオープンしたことですし、これからは社内システムやステートレスではない言ってしまえばレガシーなアプリケーションを移行するようなケースも増えてくるだろうと思いますし、そうなると必要になってくるんだろうなと。

ということで、AppEngineで作ったのですが、これがなかなか良い感じにできました。

EC2における手法の問題点

既知のEC2の手法には問題がいくつかありますが、これらがAppEngineを使うと良い感じに解決できます。

インスタンス数の増大による処理時間の増加

一般的にはそうそう問題とはならないでしょうが、大量のインスタンスを利用する規模の大きい企業や、GCPで代行ビジネスを行ういわゆるMSPのような立場だと、インスタンス数が増えてくると全部深夜のうちに終わらせたいのに終わらない可能性が出てきます。

APIエラー時のリトライ

もちろん確率としては低いですが、失敗することはあります。これも台数が多くなると顕著になってきます。APIのエラーハンドリングをキチンとやればという話ではあるのですが、エラー時にWaitなんかを入れだすとまた上記の話で処理時間が増えていって想定時間内に終わらない可能性が出てきます。

AppEngineでの構成

このような構成になります。公式アイコンが出たのでさっそく使ってみました。

f:id:FumblePerson:20161225004509p:plain

cloudplatform.googleblog.com

解説

ポイントを解説していきます。

定期実行

AppEngineにはタイマー機能があるので、これを使用します。バッチ処理の実行はキューなどを使っていくらでも冗長性を持たせられますが、発火元の冗長化は自力でやるととてもツラい領域なので、お任せできるのは嬉しいですね。

Scheduling Tasks With Cron for Python  |  App Engine standard environment for Python  |  Google Cloud Platform

分散処理

対象のリストアップとバックアップの実行を分けて、バックアップの実行をTaskQueueに詰めて分散で実行させます。これによって、単一のインスタンスで処理する際の処理時間の問題が解決できます。また、PushQueueを使用するとバックエンドのインスタンスをポーリングさせたり余裕を持って起動しておく必要がなくなるのがとても楽で良いですね。よくあるキューシステムではできない形なので、AppEngineのお気に入り機能の一つです。

自動再実行

TaskQueue (PushQueue)は処理に失敗すると間を置いて再実行をしてくれます。連続で失敗すると再実行間隔も調整しながら実行してくれるので、自力でExponential backoffのような実装をしなくて良いのでとても楽です。

yoshidashingo.hatenablog.com

権限管理が楽

AppEngineというかGCP自体の良さという感じではあるのですが、アカウントを跨いだ権限を与えて複数のアカウントのバックアップを一括で管理したい場合に、AppEngineのアプリケーションに自動で付与されるサービスアカウントのメールアドレスを該当のプロジェクトに招待して権限を付与してあげるだけなのが楽ですし、APIキーの共有などの煩わしさからも開放されるのでとても良い感じです。

まとめ

このような、AppEngineとTaskQueueでタスクの列挙と実行を分散で行う手法は、色々と応用が効きそうです。また、お任せできる領域が多いので、実装や管理が非常に楽になるのも大きなメリットです。AppEngineはWebアプリケーションを乗せるPaaSに留まらない便利なサービスなので、今後も積極的に使っていきたいです。

このシステムは既に私の手を離れており、手元にソースコードが無いのが残念ですが、暇を見つけてまた実装して公開したいなと思います。

AWS Lambdaのソースコードを覗いて動作原理を知る試み

この記事は、Serverless Advent Calendar 2016の14日目の記事です。

qiita.com

昨日は工藤さん(level69)による、「Serverless Meetup Sapporoで話てきた」でした。
工藤さんは元々北海道出身という縁もあることから、先日のServerless Meetup Sapporoに自腹で遠征してくれました。本当にありがとうございます。

serverless.connpass.com

本題

さて、本題ですが、最近はApache (元IBM) Openwiskを筆頭に、IronFunctionsなど、OSSなFaaS実装が増えてきていますが、ふと「Lambdaはどうなってんだろーなー。あ、ていうかLambdaもPythonなら、スクリプト言語だからある程度まではソースコード読めるんでは?」と思い立ったので、じゃあやってみましょうという試みです。

まずはFunctionの呼び出し元を知る

Functionの中でスタックトレースを出力すれば、Functionの呼び出し元が分かるはずです。Lambdaのソースコードの旅はここから始まる!ということで、出力してみましょう。

以下のようなFunctionをアップロードして実行します。

import traceback

def lambda_handler(event, context):
     traceback.print_stack()

結果を見てみましょう。

$ lamvery invoke {}
START RequestId: ed0dbfd9-c134-11e6-92b1-ab90b641e77e Version: $LATEST
  File "/var/runtime/awslambda/bootstrap.py", line 426, in <module>
    main()
  File "/var/runtime/awslambda/bootstrap.py", line 423, in main
    handle_event_request(request_handler, invokeid, event_body, context_objs, invoked_function_arn)
  File "/var/runtime/awslambda/bootstrap.py", line 243, in handle_event_request
    result = request_handler(json_input, context)
  File "/Users/marcy/project/lambda-source/lambda_function.py", line 4, in lambda_handler
END RequestId: ed0dbfd9-c134-11e6-92b1-ab90b641e77e
REPORT RequestId: ed0dbfd9-c134-11e6-92b1-ab90b641e77e  Duration: 0.55 ms   Billed Duration: 100 ms     Memory Size: 128 MB Max Memory Used: 15 MB  

/var/runtime/awslambda/bootstrap.py がLambda Function実行時の真の起点となるソースのようです。

起点のスクリプトをダウンロードしてみる

では、起点のスクリプトが分かったので取得してみましょう。Lambda:invokeFunctionAPIで得られる標準出力の内容は一定の長さを超えると切られてしまうので、S3に入れてみることにします。

以下のようなFunctionをアップロードして実行します。

import boto3

s3 = boto3.resource('s3')

def lambda_handler(event, context):
    s3.Object('marcy-terui', 'bootstrap.py').put(
        Body=open('/var/runtime/awslambda/bootstrap.py', 'rb'))

起点のスクリプトを読んでみる

さて、S3に入ったファイルを落として読んでみましょう。すると、まず冒頭でPythonの標準モジュールではないものをimportしています。特にimport前にsys.path.append 等を行っている記述はないので、同じディレクトリ内にあるはず。

同じディレクトリ内のファイルを全て取得する

以下のようなFunctionをアップロードして実行します。

import boto3
import os
s3 = boto3.resource('s3')

def lambda_handler(event, context):
    for f in os.listdir('/var/runtime/awslambda/'):
        s3.Object('marcy-terui', f).put(
            Body=open('/var/runtime/awslambda/{}'.format(f), 'rb'))

これで全て取得できました。

f:id:FumblePerson:20161214115851p:plain

え?ソースコードを見せろって?

はい。おっしゃる通りで、ソースコードを見ながらあーでもないこーでもないっていうのをやりたかったんですが、ソースコードに思いっきり Copyright (c) 2013 Amazon. All rights reserved. って書いてあるので、勝手に公開したら怒られかねないなと思いとどまりました。。。

気になる方は是非上記方法を試して見てください。一部 so 化されていますが、大部分は普通のPythonコードです。まだ全てをちゃんと読み込んでないですが、起動されたLambdaプロセスが呼び出しを待ち受ける方法が1つではないことが分かったりして、中々面白い発見があると思いますよ。何か最適化のヒントとなる情報などが読み取れたりするかも?(これくらいは言っても怒られないはず?)

簡単ですが、以上です

本当は、以下のようなのをある程度完成させてお披露目したかったんですが、Serverless Meetup SapporoやYAPC::Hokkaidoの諸々やら仕事がわりと立て込んでたりで進捗ダメです・・・年内には目処を付けたい。。。

とりあえず、こちらからは以上です