com4dc’s blog

Javaプログラマーのはずだけど運用してます

2023年

もう半分以上過ぎているが何をやっていたかをメモ

仕事

  • 2023年から新規サービスの立ち上げ
    • 完全新規で技術スタック選定からアーキテクチャの決定等なんか色々やっていた
    • 安易なマイクロサービスやめた
    • プロジェクトが3か月停滞した。誰も決められない現実があったので、ひたすら決定するロールへチェンジ
    • ADRを初めてまともにつけた
    • チーム内のいざこざでコミュニケーションが断絶したので、自分が全部中継して破綻した
    • IdpにCognitoを採用したが後々これがつらいことになった
    • 決めるんだけど、毎度その判断正しかったんかなぁとなる。しかしプロジェクトを前に進ませるためには誰かがやらんといかん

転職してた

  • 上の新規プロジェクト立ち上げで半年くらいやってたが、なんとなく流れで5月に転職してた
  • 引継ぎ先の人材がおらず、引継ぎ期間1週間かつ、有休を10日くらい残して辞めることになった
  • 特に大きな不満があったわけではないが、楽しそうだったので。
  • なんだかんだ転職は2回目
  • 30歳直前で一回目、そして今回40歳手前で二回目
    • まったく意図してなかったが10年スパンくらいで環境を変えている
  • とても楽しいフェーズ

リモートワーク

  • 本社が東京なので引き続きリモート
  • リモートやればやるほどハードな働き方であることを実感する
  • 万人にお勧めできる働き方ではない

40になった

  • 40になって開発経験を生かしてうまく立ち回れている
  • 20代、30代とは違う動きを期待されている
  • どこかでリモートワークと40代の働き方とかについて自分の考えまとめておく

問題解決力を高める推論の技術という本を読んだ

問題解決力を高める推論の技術という本を読んだ。

 

読書時間

 

大体2日ほど

 

内容について

 

帰納法演繹法、仮説的推論等々を紹介している。具体例を交えながら、日常に意識的に取り入れて習慣化できるよう、どういうシーンでどのような思考でどのような法則や仮説が得られるかといった話題が多かった。

 

帰納法演繹法いずれも、読んでみると意外と日常的に行なっていることであり、明文化して改めてそれに名前がつけられると、「ああ、あれはそういう思考法だったのか」くらいの感じで特に違和感なく読めた。

 

プログラムを書いたことがあるならば、複数の事項から一般化した共通の事項を見つけるといったプロセスは、何かと自然にやっている気がする。

  日々なんとなくみているものに疑問を持ち、その現象が「なぜそうなっているかを考える」というのは確かに意識しないとなかなかできない、という箇所は心に刺さるものがあった。  

微妙だった部分

とにかく具体例がしっくりこないものが多かった。

身近な話題からそれぞれの思考法で法則や予測を導き出すという試みは、一見すると難解な思考法も気軽に適用して鍛えることができるよ、というのをアピールしたい意図は伝わるのだが、

 

池上彰の番組が人気なのは世間が知的スッキリ感を求めているからだ

 

という具体例あたりで、思い込みの域から出ないんじゃないかなぁという気持ちがあり、それ以外の具体例もいくつかしっくりこないものがあった。

 

ただ、前半の物理的なものごとからその物体がもつ特徴を抽象化し、それを一般化するといった思考の例にあがっている「水」に関しての例に関しては、なるほど、といったこともあるので、須く全ての具体例が悪いわけではないと思う。

 

気になる部分は多いものの読んで時間を無駄にしたという気はしなかったのでこれはこれでよし。久しぶりに技術書(これも一種技術書かもしれないけど)を読んで楽しかった。

ホームベーカリーにハマった

コストコでホームベーカリーがやすかったので思わず買ってしまった。以下が同じ種類

元々朝食はひたすらにパン食なため、割と食パンの消費量は半端ない。

関東にいた頃は祖父母宅で毎朝出ていたアンデルセンのイギリスパンが大好きだったのだが、残念ながらアンデルセンが北海道になさそう。ただ、北海道は日本随一の小麦の生産地なため当然ながらパン屋が豊富だった。こちらに移り住んで最初の家の近くにボストンベイクという、焼きたてパン屋なのだがアホなのかというくらい安いパン屋があったため、ほぼ毎朝そこでパンを買ってから出社していた。それ以外にも地元密着のパン屋がいくつもあり、パン党の自分としてはとても嬉しい限り。

最近ではペンギンベーカリーカフェの食パンが非常にお気に入りなのだが、一斤で300円ほど。少々値が張る。

そこでせっかく小麦粉の生産地が近くにあるのだからということで自作をしてみることにした。いろんな食パンを食べてるうちに「春よ来い」とか「ハルユタカ」とか北海道産の小麦粉を色々知り、これらで作られた食パンが非常に好みだということもわかってきた。

そこで1から自作することにした。最初はホームベーカリーのミックス粉を使って食べてみるがなかなかうまい

レシピ

Siroca の食パンミックス

ちょっとサイズが小ぶりなので、自分の機種でやると少し背が低い食パンになる。甘みがあるけどくどくなくて軽く食べれて良い。1歳ちょいの娘も何もつけずにパクパク食べていた。

ぼーのの食パンミックス

個人的にはこれが一番美味かった

item.rakuten.co.jp

ただし3袋で980円くらいするので、少々お高い部類ではある。しかし、小麦の味がよく感じられるのと粉の量が多目なのかそこそこ背の高い食パンらしいいで立ちで出来上がる。原材料を見ると粉末はちみつが入っているので、0歳児等は注意が必要。

うちの娘は知らずに食べてしまったが、こちらもパクパク食べていた。こころなしかSirocaの食パンよりもおかわり要求が強かった気がする。

自作レシピ

パンミックスは水を入れるだけで、とても便利だが、まあ少々お高い。そこで粉も自分で調合するように変えた。参考にしたレシピは以下。

oceans-nadia.com

分量を見ると他のレシピより、砂糖とバターが若干多め。そのため少々甘めの仕上がりになる。これは非常に美味い。

無塩バターがない場合は、エキストラヴァージンオリーブオイルで代用できるようだ(10gほど?)今度やってみようかと思う。

同じ部屋で寝ている場合はきつい。混ぜる際の音がそれなりに騒音である。ウオンウオンという音にネコも当初は非常にビビっていたが、今や慣れたものである(いたずらされなければ何でも良い)

調理時間

大体、食パンの通常時間は4時間ちょい。そのため、朝焼き立てが食べたいと思った場合は、3時くらいから動くようにタイマーをセットすれば良い。ただ、焼き立て直後は柔らかくてなかなか切るのが難しいので、焼き上げてから1時間ほどのベンチ時間を見ておいたほうが良さそうだ。従って、実際に窯から出してから1時間後に食べられると思ったほうが良さそう(1時間後なら普通の包丁でも切れた。焼き上げ直後は柔らかすぎて潰れる)

コスト

パンミックスを使うと買うより少々割高である。最低でも1斤につき200円弱と考えると、高い部類ではある。自作の場合は100円以下まで下げられそうだが、作る時間やらの手間暇を換算するともしかすると割高になるかもしれない。そのため、以下のような奇特の人以外はやめたほうがよいかもしれない

  • 材料を全部自分で把握した上で作ってみたい
  • 別に多少の手間なら無料と換算するわ
  • 焼き立てパンにまさるものはない。プライスレス
  • 朝パンがないと絶望だ

とりあえず

購入して一ヶ月ほど楽しくパンを焼いている。粉の調合さえ面倒でなければ自分でやったほうが遥かに安くあがるようだ。

「とてつもない失敗の世界史」を読んだ

とてつもない失敗の世界史

とてつもない失敗の世界史

とてつもない失敗の世界史

気になっていたので購入していた本の一つ。 有給を使って休んだものの札幌はあいにく天気が悪く、どこへでかける気力もなかったので積ん読消化にあてた。

結論から言うと、少々話題がとっちらかっている感じはあるがとても面白かった。特に今のコロナ禍で様々な正義が錯綜している中、みんなこの本読んで落ち着いた方が良いんではないかな。人間が間違った方向に進むときは、たいてい情報を収集するものの自分に都合の良い情報だけを選択し、都合の悪い情報や反論などは取るに足らないものとして無視するか、過剰に攻撃して封殺するかなど、ああ、歴史上何度も繰り返してきてるんだなというのがよく分かる。

この本は、徹頭徹尾、人間はしょうもない種族であるということを一貫して主張していてとても良い。ここまで歴史上の小さな失敗から大きな失敗までを寄せ集めたエピソード集はなかなかない。内容としては、本当にしょうもない失敗やら妄想によって些細な結末から、国が傾くもしくは消滅してしまうレベルの結末まで様々なバリエーションが紹介されている。

名前を聞いたことがある身近な人から、そんな人が歴史上いたのかと発見することもあり、単純に知識が増えるのは面白い。

史書ではないので事実を淡々と書くというよりかは、エピソード一つ一つに対して都度茶化した記述になっているので(植民地政策の奴隷制度のところはそんな茶化した記述にはなっていないが)割とサクサク読めてしまう。日本史、世界史が大の苦手だった自分がスラスラ読むことができたのは、面白おかしく(ときにはエピソードを想像で補いながら)記述されているのが大きかった気がする。全面的にこれが正しい世界史と思うわけではないが、単純に出来事と登場人物、そこにまつわる失敗談を知ることができたので、興味の取っ掛かりとしては良かったかもしれない。

どうもこのシリーズはいくつか出ているのようなので他のも買って読んでみようかと思う。

とてつもない噓の世界史

とてつもない噓の世界史

ClientHttpRequestInterceptor で RestTemplate のリクエスト処理に割り込む

やりたいこと

RestTemplate をつかう際に共通処理をリクエスト前に潜り込ませたい。例

  • ローカルキャッシュに持ってる認証情報を Authorizationヘッダ にセットする
  • 認証情報がなければ取りに行ってローカルキャッシュに保存しつつ Authorizationヘッダにセットする

ClientHttprequestInterceptor インタフェース

RestTemplate には Interceptor を複数設定できるようになってるので、このインタフェースを実装すれば良い。

docs.spring.io

Mock でちゃんと Intercept されるか確認してみる

動作環境は以下

  • openjdk 11.0.6 2020-01-14 LTS
  • macOS Mojave 10.14.6
@ExtendWith(MockitoExtension.class)
public class DummyClientTest {

    @Mock
    TestClientInterceptor interceptor;

    @DisplayName("Interceptor内で処理が行われることを確認")
    @Test
    public void inject_interceptor_success() throws IOException {

        String success = "success";
        var mockResponse = new MockClientHttpResponse(success.getBytes(Charset.defaultCharset()), HttpStatus.OK);
        when(interceptor.intercept(any(), any(), any())).thenReturn(mockResponse);

        var restTemplate = new RestTemplateBuilder().additionalInterceptors(interceptor).build();

        var value = new LinkedMultiValueMap<String, String>();
        value.add("main", "request");

        var requestEntity = RequestEntity
            .post(URI.create("https://example.com/dummy"))
            .body(value);

        var actual = restTemplate.exchange(requestEntity, String.class);
        assertNotNull(actual);
        assertEquals("success", actual.getBody());
    }
}

RestTemplate に何も設定しなければ https://example.com に謎の情報をPostしてしまうので当然エラーで弾かれる。今回は Interceptor 実装を RestTemplate に設定してるのでそちらに処理が委譲されてるはず。 このUnitTestは通る。

23:38:31.434 [Test worker] DEBUG org.springframework.web.client.RestTemplate - HTTP POST https://example.com/dummy
23:38:31.448 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/cbor, application/*+json, */*]
23:38:31.453 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Writing [{main=[request]}] with org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
23:38:31.472 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
23:38:31.474 [Test worker] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/octet-stream"

ちなみにInterceptor を設定しない場合

23:26:16.094 [Test worker] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {s}->https://example.com:443] can be kept alive indefinitely
23:26:16.094 [Test worker] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
23:26:16.095 [Test worker] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://example.com:443][total kept alive: 1; route allocated: 1 of 5; total allocated: 1 of 10]

404 Not Found: [<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w... (445 bytes)]
org.springframework.web.client.HttpClientErrorException$NotFound: 404 Not Found: [<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w... (445 bytes)]
....

めっちゃエラーで怒られる。ごめんなさい。一応ちゃんとテストで404ナノを確認

    @DisplayName("存在しないURIにアクセスし404が返却される")
    @Test
    public void not_inject_interceptor_failed_notfound() {
        var restTemplate = new RestTemplateBuilder().build();
        var requestEntity = RequestEntity
            .get(URI.create("http://dummy.restapiexample.com/api/v1/dummy")).build();

        var actual = assertThrows(HttpClientErrorException.class, () -> restTemplate.exchange(requestEntity, String.class));
        assertNotNull(actual);
        assertEquals(HttpStatus.NOT_FOUND, actual.getStatusCode());
    }

実装

雑な実装とイメージ。

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
        throws IOException {
    
    // どこか保存したところからATを復元する
    var accessToken = repository.findToken().orElseGet(() -> {
       var token = getNewAccessToken();
       if (token != null) { repository.saveToken(token); }
       return token;
    );
    
    if (accessToken == null) {
        log.warn("can not get AccessToken.");
        throw new TestClientException("can not get AccessToken");
    }
    
    // Bearerトークンとして付与
    request.getHeaders().add(HttpHeaders.AUTHORIZATION, "bearer " + accessToken.getAccessToken());   
    
    var response = execution.execute(request, body);
    var statusCode = response.getStatusCode();
    
    if (statusCode.equals(HttpStatus.UNAUTHORIZED)) {
        // ATが無効. リトライは一回でいいか
        log.info("Expired AccessToken. Try call TokenEndpoint.");
        
        var token = getNewAccessToken();
        
        if (token == null) {
            log.warn("can not get AccessToken.");
            throw new TestClientException("can not get AccessToken");
        }
        // どこか共有するとこにATを保存する
        repository.saveToken(token);
        
        var renewToken = token.getAccessToken();
        request.getHeaders().add(HttpHeaders.AUTHORIZATION, "bearer " + renewToken);
        response = execution.execute(request, body);
    }
    return response;
}

トークン取得、キャッシュ保存、リクエストが何箇所か重複してるのできれいではない気がするんだけど、 とりあえずやりたいことはできた。

3分間ネットワーク基礎講座を読んだ

IPA基本情報技術者試験応用情報技術者試験を取る際に一通り学んだはずだし、大学でも一通り入門はしたはずなのだがやはり何年も使ってないと次々と忘れてしまうネットワーク周りの話。

今や AWSGCP 等によってアプリケーションエンジニアでも気軽にインフラ環境を手に入れることができるのだが、ネットワーク周りの設計等は正直公式リファレンスから毛の生えたような程度の設定でなんとなく動かせてしまっている。なんでこういう設計にしたのかがちゃんと説明できないなどなど、なんだかんだ基盤周りの知識はないと困るので、まずはネットワークから復習のために買った

読んだもの

[改訂新版] 3分間ネットワーク基礎講座

[改訂新版] 3分間ネットワーク基礎講座

  • 作者:網野 衛二
  • 発売日: 2010/09/11
  • メディア: 単行本(ソフトカバー)

読了までかかった時間

3時間くらい

雑な感想

物理層からアプリケーションレイヤーまで一通りエッセンスをギュッとまとめて紹介してくれていて、復習にはとても良い。読んでいてわかったのだが、どうやら自分はレイヤー3以下が苦手らしい。ルーティングプロトコルデフォルトゲートウェイなどの話は、単語は聞いたことあるしどういう設定すれば動くのかわかるけど、実際中で何をやっているのか説明ができない、という状態だったのでそのあたりざざっと復習ができたのでとても良かった。

正直普段アプリケーションを開発している側からすると、TCP/IPあたり以降の話しかあまり意識したことがなく、それこそ自前でネットワークを組んでみるとかしない限りはうすらぼんやりとした知識しかないのが正直なところだ。そういう意味ではネットワークのレイヤー構造は下位レイヤーの隠蔽がうまくいってるんだなぁと感心する。

スイッチ、ハブ、ルーターはその昔はそれなりに研究室に転がっていたりして、なんとなく研究室内のネットワークへつなげるために利用していたが、それこそあまり使い分けは意識していたわけではない(さすがにルーターとハブの違いくらいはわかっていたが)

ただ、やはり当然ながらそれぞれの機器にはそれぞれの利点と不利な点があるなど再度色々と勉強できたのは良かった。以下途中からだけど雑なメモ

  • RIP
    • ルーティングプロトコル
    • ルーティングアップデート
    • ルーティングテーブルを30秒に一回交換し合う
    • 6回受け取らなかったらルートからそのルーターを削除
  • メトリック
    • 最適な経路を決定する際の判断基準
    • ホップ数、回線スピード、込み具合、エラー率とか
  • ICMP
  • IPデータグラムのTTL
    • ルーターを通るたびに-1
    • Linuxは64、Windowsは128
    • 通常30くらいですべて到達可能なので十分不正な数値となりうる
  • タイプ3
  • ping
    • タイプ0
      • エコー応答
    • タイプ8
      • エコー要求
  • タイプ11
    • Time Exceeded
    • TTLきげんぎれ
    • traceroute
      • チェック用Software
      • 宛先までのルートを調べられる
      • 悪用されるといろいろまずいのでルーター管理者はICMPの扱い注意
  • レイヤー4
    • 到達後の話
    • エラー回復
    • フロー制御
    • 届いたデータがどのアプリケーションのものかわからんのでポートで振り分け
    • TCPUDP
  • TCP
    • TCPヘッダ=20オクテット
    • データ転送の許可要求
    • 3wayハンドシェイク
    • MSS
      • Max Segment Size
    • シーケンス番号
    • シーケンス番号は送るデータの先頭オクテット番号
    • 確認応答番号は次に送ってほしいデータの先頭オクテット番号
    • RTT=Round Trip Time
    • ウィンドウ制御
      • 複数のセグメント転送 → 確認応答
      • バッファで一時的に保管
      • ウィンドウサイズ=バッファ量=確実に受け取れる最大のデータ量
    • Wellknown Port
    • 送信元は49152~の重複してないポートを利用する
  • UDP
    • 何も制御しないのでめちゃくちゃ早い
    • ブロードキャストはUDP
  • クラスレスアドレッシング
  • NAT
    • ネットワークアドレス変換
    • 同時接続数の限界=割り振られたGlobal IPの最大値
    • NATの発展したもの=NAPT
  • NAPT
    • ネットワークアドレスポート変換
    • ポート番号も変換対象なので同じGlobal IPから複数のアプリケーションが外に出れる
    • IP以外にポートも正しくないとデータが流れないので、より安全
    • 内側から外へアクセスする分にはIP、ポート番号が記録されるので問題ない
    • 外側から内側へアクセスするときには、変換テーブルがないので無理
      • LAN内部に80ポートの公開サーバーがある場合等
    • 静的NAPT
      • あらかじめIPとポート番号の変換を書いておく

Go Lambda + aws cdk でLambda実行エラー

最近コードを書く機会が極端に減ってしまっている(読む機会はたくさんある)ので、業務外で会社の人たちがたくさん公開しているLambdaの資料を見ながら、お酒飲みながら雑なコードを書いてなるべく筋トレしています(圧倒的に記述量が足りないので色々忘れる速度の方が早いのが悩み)

しょうもない話です

少し考えればわかることでしたが。以下しょうもない経緯の話なのでここに結論を書いておきます。

aws-cdk はほぼ関係ありません。

macで単純に go build したバイナリをLambdaとして登録すると「当然アーキテクチャやらOSが違うからまともに動かないので、ちゃんとアーキテクチャやOSを指定してターゲットに合わせてビルドしたものを登録しようね」という話でした。

$ GOARCH=amd64 GOOS=linux go build -o bin/main

これを指定しないと以下のエラーが出力され、何をやってもLambdaが起動しません。

{
  "errorMessage": "fork/exec /var/task/main: exec format error",
  "errorType": "PathError"
}

stackoverflow - exec format error when running AWS Golang Lambda

"exec format error" from SAM Local with Golang on Mac

Qiita - alpineのイメージでAWS Lambda用のgoバイナリをビルドしたらPathErrorになった話

長らく環境に合わせたバイナリを作って云々というのをやっていなかったのもあって、このあたりの考慮がさっぱり抜けてました。

経緯

Lambda を書く際には Typescript が圧倒的に多いようですが、自分の場合なかなか慣れることができておらず、別の言語(大体 Go )を選択することが多いのです。

aws-cdk は TypeScript を書きつつ、Go で Lambda を Deploy した際に変なところでハマったので記録しておきます(多分頭が回っていなかった)

go lambda + cdk の構成

こちらの記事を参考にさせていただきました。

aws cdk でGolangのLambdaをデプロイしてみる

ふむふむ。わかりやすいですね。これなら悩むことなくササッとできそう。

バージョン情報は以下のとおりです。

$ cdk --version
1.51.0 (build 8c2d53c)

基本的に cdk の作り方はいつもどおり。

$ mkdir lambda-go
$ cd lambda-go
$ cdk init --language=typescript

雛形ができました。

go lambda

Lambda は handers というディレクトリを作成し、こちらの配下に配置することにしました(このあたり、ちゃんと読まずに適当にやっていることがバレる)

package main

import (
    "context"
    "fmt"

    "github.com/aws/aws-lambda-go/lambda"
)

// MyEvent lambda用カスタムEvent
type MyEvent struct {
    Name string `json:"name"`
    Age  int    `json:"How old are you?"`
}

// MyResponse lambda用カスタムレスポンス
type MyResponse struct {
    Message string `json:"Answer:"`
}

// HandleRequest Lambdaリクエストハンドラー
func HandleRequest(ctx context.Context, event MyEvent) (MyResponse, error) {
    return MyResponse{Message: fmt.Sprintf("%s is %d years old!", event.Name, event.Age)}, nil
}

func main() {
    lambda.Start(HandleRequest)
}

go の Lambda パッケージは導入済みとしています。必要であれば go get github.com/aws/aws-lambda-go/lambda して導入しておきます。

参考にさせていただいた記事の通り記載しました。記述した go のコードはビルドしてバイナリに変換します。はい。もう一度いいます。変換します。

$ go build -o bin/main

生成したバイナリは handlers/bin/main に配置されました。Lambda の準備が完了したので Lambda を配置する Stack のコードを修正します。

lambda-go-stack.ts

@aws-cdk/aws-lambda を導入しておきます。

import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';

export class LambdaGoStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
    new lambda.Function(this, 'GoFunction', {
      runtime: lambda.Runtime.GO_1_X,
      handler: 'main',
      code: lambda.Code.asset('./handlers/bin')
    });
  }
}

runtime に GO を指定し、 handler には main, code には変換したバイナリが配置されている相対パスを指定します。

stack を編集した内容を変換する必要があるので、npm run build を実行すると、jsコードに変換されます。参照した記事にもあるように、tsc; する際に同時に go build をしてくれると楽なので package.json を以下に変更。

  "scripts": {
    "build": "tsc; cd handlers; go build -o bin/main",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },

これで npm run build した際に同時に go build も実行してくれるようになりました。ではcdk deployします。

$ cdk deploy --profile hoge
LambdaGoStack: deploying...

....
arn:aws:cloudfromation:us-east-1:xxxxxxxx

Lambdaをテストするとエラー

簡単なコードだから普通に動くだろうと高を括っていたので、以下のエラーを見て鳩が豆鉄砲を食ったよう。全く想定外。

{
  "errorMessage": "fork/exec /var/task/main: exec format error",
  "errorType": "PathError"
}

exec format error なんだかそもそも根本が間違ってそうなエラーメッセージ。

そういえば Go の Lambda ってソースコード上げてるわけじゃないしあたりで気づいた結果、そもそも起動する対象がmacと違うというのに今更気づき、調べてみた結果が冒頭の結論です。

まとめるほどでもないもの

冒頭の結論が全てです。

バイナリを生成するタイプのものは、どこで動かすのかをきちんと意識してやりましょうという話でした。

2020/07/19 追記

GoのLambdaの公式リファレンスにちゃんと書いてあった。

docs.aws.amazon.com

GOOS を linux に設定すると、非 Linux 環境でコンパイルする場合でも、コンパイルされた実行可能ファイルと Go ランタイムとの互換性を確保できます。

ドキュメントを読め。本当にすいませんでした。

参照