
ALBログ転送Lambdaを、17個まとめて葬りました
こんにちは。コネヒトでソフトウェアエンジニアをしているしょうぽんです。
今回は、社内のLT会でも話す予定の「ALBのアクセスログをElasticsearchに転送しているLambdaを、17個まとめて葬ってきた話」をブログ用に書き直してみます。タイトルだけ聞くとちょっと物騒ですが、ちゃんと供養(統合・整理)してきましたのでご安心ください。
この記事で一番お伝えしたいのは、実は技術的な中身そのものではありません。「誰かに頼まれたわけでもない地味な負債返済を、エンジニアが自分の判断で進められて、しかもそれが社内でちゃんと賞賛される」という、コネヒトの仕事の空気感です。
何をしたのか:17 → 1
コネヒトでは、各サービスのALB(ロードバランサー)のアクセスログを ALB → S3 → Elasticsearch という流れで集約し、Kibanaで見られるようにしています。このうち「S3に届いたログをElasticsearchへ転送する」部分を、Lambdaが担っていました。
問題は、その転送Lambdaが サービス × 環境ごとに1個ずつ 存在していたことです。alblog2elasticsearch-a-service-api、alblog2elasticsearch-b-service-api、alblog2elasticsearch-c-service-api……と、同じようなLambdaがずらりと並び、その数はなんと17個でした。
中身を覗いてみると、ほとんどがコピペで、差分はS3のprefix(パス)とElasticsearch側のpipeline名くらいでした。今回はこれらを prefixからサービスを判定する汎用Lambda に統合しました。結果、17個がたった1個になりました。
なぜこうなったのか:コピペで増える構造は、必ず増える
この構成、作るときはとても楽なんですよね。新しいサービスを足すたびに既存のLambdaをコピーしてパスを差し替えるだけ。でも運用フェーズに入ると、この「ラクさ」という甘い毒がじわじわと本性を表してきます。
一番つらいのが、Lambda上のPythonランタイムサポート終了(EOL)です。EOLが来るたびに、17個全部のランタイムに対応して回らなければなりません。1個ずつは小さな作業でも、17回繰り返すとなると、それなりの心理的ハードルになります。
この統合自体は私が最初に思いついたわけではなく。数年前から先輩エンジニアたちが「これ、なんとかしたいよね」とチャレンジしては頓挫してきた、いわば社内に長く横たわっていた負債でした。
なぜやったのか:ちょっと燃え尽きていたから
この負債返済に自ら着手した背景には、私自身のモチベーション維持という目的もありました。
担当しているレシートエールのメイン機能リリースが終わり、少しだけ燃え尽き症候群のような状態になっていました。次の開発もそれなりに大きなテーマで、クイックウィン系のすぐに完了させて何かしらの手応えを得られる性質のものではありませんでした。
大きなタスクが手元で滞留していると、いわゆる ツァイガルニク効果 によって、強い心理的ストレスを長く抱え続けることになります。そういうときに、「何かを最後までやり切った」という達成感を得るための“逃避先”として、こうした簡単にできそうな負債返済タスクを自分用に用意しておく。これが私なりの やる気のマネジメント だったりします。
AIによってメインの実装が爆速で終わるようになった今、こうした「解消すべき負債リスト」を自分の中に持っておくと、モチベーションの波をうまくならせるのでおすすめです。
技術的な中身:prefixで分岐する汎用Lambda
やったこと自体はシンプルです。S3イベントから飛んでくるオブジェクトのprefixを見て、その場で「どのサービスのログか」「どのindexに入れるか」を判定する汎用Lambdaに作り替えました。
ざっくりとしたイメージでお伝えするとこんな感じのことをやっているわけですね。
# alblog/{prefix}/AWSLogs/... からプレフィックスを動的に取得 prefix = key.split('/')[1] es_index = prefix + "-alblog-" + datetime.strftime(datetime.now(), "%Y.%m.%d") pipeline = PREFIX_TO_PIPELINE.get(prefix, prefix + "-alblog") s3 = boto3.resource('s3') s3.Bucket(bucket).download_file(key, '/tmp/log.gz') with gzip.open('/tmp/log.gz', 'rt') as f: actions = [] for line in f: actions.append({"_index": es_index, "_type": "log", "pipeline": pipeline, "message": line}) if len(actions) > 3000: bulk_load(ES_HOST, actions) actions = [] if len(actions) > 0: bulk_load(ES_HOST, actions) return 'Completed'
いきなり17個を全部入れ替えるのは怖いので、進め方は次の通り段階を踏みました。
- まず
service-aだけを統合版に差し替えて、しばらく様子を見る - 問題なさそうなら、残りのサービスを順次切り替える
- 最後に、役目を終えた旧Lambda群を一括で削除する
この「1サービスだけ先行投入 → 全展開」というステップ刻みは、運用上の安全性はもちろん、心臓にもとても優しいです。
クイックにウィンできるはずのタスクで大事故は起こしたくないですからね。
ハマった地雷:命名規則はバラバラだった
きれいに片付くと思いきや、歴史的経緯による地雷を踏みました。Elasticsearchのingest pipelineの命名規則が、サービスごとに微妙に違っていたのです。
serviceA→ pipeline名はservicea-alblog(大文字が小文字に)prd-serviceb→ pipeline名はserviceb-alblog(prd-のprefixが消える)
最終的には、prefix → pipeline名の対応表を PREFIX_TO_PIPELINE というマッピングとしてLambda側に持たせ、差分を吸収しました。美しくはありませんが、現実と握手するパートです。
# S3 prefix と既存 ES pipeline 名が異なるサービスのマッピング PREFIX_TO_PIPELINE = { "serviceA": "servicea-alblog", "prd-serviceb": "serviceb-alblog", }
費用対効果の問題もありますからね。かっこいい成果物にこだわるより、いかに早く最大の利益を得るかを今回のタスクでは優先しました。
逆の選択をすることもありますが、今回のテーマはクイックウィンです。楽に早く負債を返済しましょう。
教訓は明確で、歴史的経緯は必ずコードに染み出してくるということ。特に命名の差分は、最初からできるだけ少なくしておくに限ります。
それからもいくつか工夫を重ねながら完成したのがこの、しょうぽんスペシャルLambdaです。やったぜ。

学んだこと
- コピペで増やせる構造は、コピペで増える。 これは今回のLambdaに限らず、TerraformでもCloudWatchアラームでも同じです。人間はどうしても甘えてしまいますね。
- 命名規則は大事。 経験的にも負債の多くは、命名の不揃いから生まれます。
- 先行1サービス → 全展開のステップ刻みで安全にやる。 技術的にも心理的にも、安全に進められます。
そして統合後は、新しいサービスのログを足す手順が「Lambdaをもう1個コピペして作る」から、5ステップの追記作業だけに変わりました。手順はREADMEにも残してあるので、これからは誰でも詰まらずにサービスを追加できる状態になっています。
ここからが本題:自主的にやれて、賞賛される文化
ここまではざっくりとやったことの話をしてきましたが、私がこのことをブログにしたかった一番の理由は別にあります。
この取り組み、そもそも誰かに「やれ」と指示されたものではありません。 自分のコンディションと向き合いながら、「ずっと放置されてきたこの負債、今の自分なら返せそうだぞ」と判断して、自主的に着手したものです。
コネヒトには、こうした目の前のタスク以外の改善を、エンジニアが自分の裁量で拾いにいける空気があります。そして何より、それをきちんと見て、面白がって、賞賛してくれる文化があります。
ポイントは「面白がって」というところです。「おお、ついにアレが解消されるのか」と喜ぶ古株の方もいますし、「ヘェ〜そんなことになってたんだ〜」と初めて知る方もいます。皆何かしらが改善されたということで喜んでくれるし、どう改善したのか興味津々で聞いてくれます。
実際この話は、毎月開催している社内のLT会でも発表します。「17個葬りました」というちょっとふざけたタイトルでも、温かく受け止めてもらえるゆるい雰囲気です。まだ発表してないのでわかりませんが、こうした地味な裏側の改善の価値も認めてもらえ、更にはちゃんと全員から盛大な拍手までもらえることでしょう(圧)。
冗談はさておき、こうした場所があるのはエンジニアとして本当にありがたいことだなと感じています。LT会の他にも、月に数回WinSessionというお互いに成し遂げたことを讃え合い自慢し合うセッションもあります。今回の改善のような小さなものから、新サービスのリリースやAIツールの導入など大きなものまで色々な粒度で褒め称え合います。現在は30分程度のセッションです。私は個人的には1時間にしてもいいんじゃないかな〜と思うくらい、このセッションが好きです。
負債返済は基本的に一人では完結しません。過去にこの統合へ挑んでいた先輩たちの蓄積があり、レビューやインフラ周りで力を貸してくれる同僚がいて、はじめて前に進みます。実際、今もCI/CDの整備やTerraformの差分解消、カナリアリリースの復活など、いろいろな負債返済を、いろいろな人を巻き込みながら少しずつ進めています。
コネヒトのいいところとして、Affirm & FollowというValueを掲げている点があります。これが単なるお題目という感じではなくて、実際にAffirmもしてくれるし、嫌な顔ひとつせずFollowというか並走してくれる方が多いんですよね。誰かのやっていきを尊重しつつ、それが頓挫しないように支える風潮を感じます。
おわりに
というわけで、今回は「ALBログ転送Lambdaを17個葬った話」でした。
17個が1個になった、という技術的な成果ももちろん嬉しいのですが、それ以上に、自主的に負債と向き合えて、それを賞賛し合える仲間と文化があることを、改めて良いなと思える出来事でした。
もし「うちにもこういう積年の負債があるな」という方がいたら、ぜひ自分なりのやる気マネジメントの一環として、そっと供養してみてください。終わったあとの達成感は、思った以上に効果がありますよ。