AWS Client VPNで証明書の発行・管理にEC2を使う
ウェブサービスの開発をしていると社内メンバー用のVPNを用意したくなることがある。
一番典型的な理由は、(1)AWSにあるステージング環境にアクセス制限をかけたいというものだ。あるいはユーザー向けEndpointはグローバルに開くけど、管理画面のEndpointはIP制限したいということもよくある。また、(2)IP制限をしている外部サービスにアクセスするために開発者のマシンから外にでるIPアドレスを固定したいという要件があることもある。
(1)(2)の要件を満たしつつ、AWSで完結しようと思うと、AWS Client VPNを使うことになる。その場合、サービスで使っているVPCのSubnetにAWS Client VPN Endpointを設定して、インターネットに出ていく通信はNAT Gatewayを通るようにすると、外に出るIPが固定できて、(1)でも(2)でもIP制限がかけられる。
こういう設計にしたインフラを構築することがこの10年ぐらいで複数回あり、想定どおり動いている。
毎回悩むのが、easy-rsaで発行する証明書をどこでどう管理するかということだ。証明書は更新作業もあるし、特定のClientのRevokeをすることもあるので、証明書の管理作業は年に数回あるかないかという頻度だ。
最初は作業者自身のマシンでやろうとしていたが、他のメンバーへの共有が面倒になる。複数のディレクトリに多くのファイルが生成されるのでeasy-rsaのディレクトリ以下をまるっとコピーして共有するしかないが、証明書なのでどうやって共有するか問題もあるし、ミスオペも怖いし、OSによってコマンド体系が違うのでコピペで同じ挙動を再現できるとは限らない、そもそも個人のPCでメンバー全員の証明書を発行するのが微妙、などの問題がある。
そこで最近はEC2を起動してそこで証明書の発行を行い、作業が終わったらインスタンスを停止しておき、当分使わないことが明らかならSnapshotにする、という運用に落ち着いている。
EC2だとSessionManagerでしかログインできないようにできて管理も簡単だしセキュアでもある。
他にEC2でやるメリットとしてあるのが、historyで履歴が見られることだ。もちろん作業手順は何らかの文書に残しておくべきなのだが、証明書はデフォルトでは825日でExpireするので、2年ちょっとで期限切れになる。そうなると、どこに作業手順書いてあったっけ?となる。ドキュメント含めた引き継ぎが不十分で、AWS環境へのアクセスだけは持ってるみたいなケースだと、EC2 or Snapshotに残っていればそれを起動するだけで使えて、history見ると何やってたかわかる(思い出せる)。これは大きい。
コストを最小限にするなら証明書ファイルはS3においたほうがいいかもしれないが、作業手順が増える。 EC2を使う方法はEBSやスナップショットの料金がかかるので、最善の選択肢ではないかもしれないが、運用の手間と、いざ作業するときにトラブルの可能性が低くて認知負荷が最小限にできるので、結局この方法に落ち着いている。
Rustで複数のbinをおいたときに function foo is never usedの警告がでないようにする
Rustの1つのcargoプロジェクトで、src/bin以下に複数のbinをおいていたが、いろいろな関数でfunction foo is never usedという警告が出てしまっていた。 もちろん#[allow(dead_code)]って関数やファイルの先頭に書けば警告は出なくなるのだが、dead_code検出自体は便利なので、むやみに抑制しないようにしたい。rust-analyzerが出す警告だと思うので、コンパイルに関するなにかだろうと思って、うまくやって警告そのものが出ないようにできないか調べた。
AI(OpenAI 4o)だとプロンプトにも問題があったかもしれないがうまく答えてくれなかったが、stackoverflowに参考になる答えがあった。
rust - Dead code warning with multiple binaries? - Stack Overflow
つまり、複数のbinがあるということは複数回コンパイルが走るので、そのうちのどれかから参照されていないとdead_code扱いになるということのようだ。 それを防ぐためには、呼び出すコードはlib.rsに集約してそれぞれのbinはそこから呼び出せばよい。
Streamlitでは、st.session_state.my_key = st.session_state.my_key というコードに意味がある
Streamlitのドキュメントを読んでたら、以下のコードが出てきた。
st.session_state.my_key = st.session_state.my_key
Widget behavior - Streamlit Docs
同じ変数に同じ変数を代入するっていうのに強烈な違和感を持った。 普通のプログラミングなら、まず無駄なコードってなるやつだ。 現に、Copilotにリファクタリングを指示したら消す提案をしてきた。
この挙動を実際に動かして確かめてみて、意味が理解できたのでメモしておく。
以下の例で確認。
import streamlit as st if "textbox_a" in st.session_state: st.session_state.textbox_a = st.session_state.textbox_a st.session_state.setdefault("show_textbox", True) if st.button("テキストボックス表示変更"): st.session_state.show_textbox = not st.session_state.show_textbox if st.session_state.show_textbox: st.text_input( "テキストボックスA", key="textbox_a", value="テキストボックスAの初期値", )
「テキストボックス表示変更」っていうボタンがあって、それを押すと、テキストボックスAの表示が切り替わる。
テキストボックスAでは、key="textbox_a" としているので、 st.session_state.textbox_a に入力した値を状態保持する。
ただし、Widgetは非表示になってしまうと状態はリセットされる。つまり、
st.session_state.textbox_a = st.session_state.textbox_a の部分のコードが無いとき
ボタンを押してテキストボックスを非表示にした時点で状態はリセットされ、テキストボックスAの値は「テキストボックスAの初期値」になってしまう。
コードがあるとき
ボタンを押してテキストボックスを非表示にして再度表示にした場合でも、前回入力した値が保持されている。
S3にあるALBログの調査はAthenaよりDuckDBのほうが簡単
2026/01/05追記: 続編&最新フォーマット対応版を書いた DuckDBでALBのログを集計するClaude Code Skillを作った | Kirishiki Studios
AWSのALB(Application Load Balancer)のログはS3に置かれるが、この中身をサクッと調べたいとき、Athenaを使う方法が標準的で、下記で案内されているようにパーティション射影(Partition Projection)でテーブルを作ってAthenaからクエリする。
パーティション射影を使用して Athena で ALB アクセスログ用テーブルを作成する - Amazon Athena
私も従来はその方法を使っていたが、Athenaはブラウザから使うと動作がもっさりしているし、決まったクエリを1回きり実行して結果を取得したいだけのときならまだしも、探索的にクエリを何発も実行したいときには使い勝手が悪い。
最近他のプロジェクトでDuckDBを使うようになって、使い勝手の良さに感動していたが、DuckDBはALBのログを探索的に調べたいときにもめっちゃ使えると思った。
DuckDBのインストールは公式 あたりを参照。S3のファイルをクエリする準備としてはAWS Extension – DuckDBやS3 API Support – DuckDBを参照。 端的にいうと、シェルでS3にアクセスできるクレデンシャルの環境変数がロードされている状態でDuckDBを起動して、
INSTALL aws;
LOAD aws;
INSTALL httpfs;
LOAD httpfs;
CREATE SECRET (
TYPE S3,
PROVIDER CREDENTIAL_CHAIN
);
これでロードしてあるクレデンシャルがアクセス権限を持つS3のファイルに対してクエリできるようになる。 例えば2024年11月のログをロードするなら下記のようにする。Blobのパターンを変えれば任意の期間のデータをロードできる。 この定義にあたってはパーティション射影を使用して Athena で ALB アクセスログ用テーブルを作成する - Amazon Athena にある定義を参考にした。
CREATE TABLE alb_log_202411 AS
SELECT *
FROM read_csv(
's3://[YOUR_S3_BUCKET_NAME]/AWSLogs/[YOUR_ACCOUNT_ID]/elasticloadbalancing/[YOUR_REGION]/2024/11/**/*.log.gz',
columns={
'type': 'VARCHAR',
'timestamp': 'TIMESTAMP',
'elb': 'VARCHAR',
'client_ip_port': 'VARCHAR',
'target_ip_port': 'VARCHAR',
'request_processing_time': 'DOUBLE',
'target_processing_time': 'DOUBLE',
'response_processing_time': 'DOUBLE',
'elb_status_code': 'INTEGER',
'target_status_code': 'VARCHAR',
'received_bytes': 'BIGINT',
'sent_bytes': 'BIGINT',
'request': 'VARCHAR',
'user_agent': 'VARCHAR',
'ssl_cipher': 'VARCHAR',
'ssl_protocol': 'VARCHAR',
'target_group_arn': 'VARCHAR',
'trace_id': 'VARCHAR',
'domain_name': 'VARCHAR',
'chosen_cert_arn': 'VARCHAR',
'matched_rule_priority': 'VARCHAR',
'request_creation_time': 'TIMESTAMP',
'actions_executed': 'VARCHAR',
'redirect_url': 'VARCHAR',
'error_reason': 'VARCHAR',
'target_port_list': 'VARCHAR',
'target_status_code_list': 'VARCHAR',
'classification': 'VARCHAR',
'classification_reason': 'VARCHAR',
'conn_trace_id': 'VARCHAR'
},
delim=' ',
quote='"',
escape='"',
header=False,
auto_detect=False
);
S3の対象ファイル数が多い場合テーブルができるまでにそこそこ時間はかかるが、いったんローカルにロードされればあとは速い。
- テーブルの存在を確認
D show tables; ┌────────────────┐ │ name │ │ varchar │ ├────────────────┤ │ alb_log_202411 │ └────────────────┘
- スキーマを確認
D describe alb_log_202411; ┌──────────────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐ │ column_name │ column_type │ null │ key │ default │ extra │ │ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │ ├──────────────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤ │ type │ VARCHAR │ YES │ │ │ │ │ timestamp │ TIMESTAMP │ YES │ │ │ │ │ elb │ VARCHAR │ YES │ │ │ │ │ client_ip_port │ VARCHAR │ YES │ │ │ │ │ target_ip_port │ VARCHAR │ YES │ │ │ │ │ request_processing_time │ DOUBLE │ YES │ │ │ │ │ target_processing_time │ DOUBLE │ YES │ │ │ │ │ response_processing_time │ DOUBLE │ YES │ │ │ │ │ elb_status_code │ INTEGER │ YES │ │ │ │ │ target_status_code │ VARCHAR │ YES │ │ │ │ │ received_bytes │ BIGINT │ YES │ │ │ │ │ sent_bytes │ BIGINT │ YES │ │ │ │ │ request │ VARCHAR │ YES │ │ │ │ │ user_agent │ VARCHAR │ YES │ │ │ │ │ ssl_cipher │ VARCHAR │ YES │ │ │ │ │ ssl_protocol │ VARCHAR │ YES │ │ │ │ │ target_group_arn │ VARCHAR │ YES │ │ │ │ │ trace_id │ VARCHAR │ YES │ │ │ │ │ domain_name │ VARCHAR │ YES │ │ │ │ │ chosen_cert_arn │ VARCHAR │ YES │ │ │ │ │ matched_rule_priority │ VARCHAR │ YES │ │ │ │ │ request_creation_time │ TIMESTAMP │ YES │ │ │ │ │ actions_executed │ VARCHAR │ YES │ │ │ │ │ redirect_url │ VARCHAR │ YES │ │ │ │ │ error_reason │ VARCHAR │ YES │ │ │ │ │ target_port_list │ VARCHAR │ YES │ │ │ │ │ target_status_code_list │ VARCHAR │ YES │ │ │ │ │ classification │ VARCHAR │ YES │ │ │ │ │ classification_reason │ VARCHAR │ YES │ │ │ │ │ conn_trace_id │ VARCHAR │ YES │ │ │ │ ├──────────────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┤ │ 30 rows 6 columns │ └────────────────────────────────────────────────────────────────────────────────┘
あとはいろいろクエリしてお好きなように。
select * from alb_log_202411 where elb_status_code != 200 LIMIT 1;select timestamp, request, elb_status_code, target_status_code, domain_name from alb_log_202411 where elb_status_code != 200 LIMIT 100select timestamp, request, elb_status_code, target_status_code, domain_name from alb_log_202411 where elb_status_code != 200 and domain_name != 'foobar.com' LIMIT 100
みたいに探索的に調べるのが高速にやれるのが最高。
※補足
30カラムもあるので、デフォルトではコマンドラインで全部のカラムを表示できない。
なので、SELECT句でカラムを絞るか、 .mode line とか .mode box とかで全カラム表示できる。
デフォルトに戻すには .mode duckbox。
どういう選択肢があるかはOutput Formats – DuckDB見ると分かる。
※補足2
ググったら似たことしている記事あったのでリンクしとく
Analysing AWS Application Load Balancer Logs with DuckDB: Unleashing Performance Insights
Rustのglibc依存問題
Rustはコンパイルできれば動くと思いきや、そうでもない。 cargo-lambdaを使って、ローカル環境で動作確認したlambda関数をデプロイしたら、動かなかった。
/var/task/bootstrap: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /var/task/bootstrap)`
Amazon Linux2(AL2)とAmazon Linux2023(AL2023)のそれぞれのglibcのバージョンは ツールチェーンの改善: gcc、binutils、 glibc - Amazon Linux 2023 に記載のとおり、2.26, 2.34なので、AL2だとエラーが出たということだった。AL2023でデプロイしなおしたら問題なく動いた。
aws-lambda-rust-runtimeのREADMEを見ると al2023を使うことを勧めている。
この記事もみつけた。 cargo lambda deployした関数が `GLIBC_2.28' not found でうごかない
ビルド時に--target を指定することで解決できるようだ。
cargo lambda build --release --target aarch64-unknown-linux-gnu.2.26
下記の記事ではいろいろな選択肢がリストアップされているが、結局のところどれが良いかはアプリの実装内容や、環境や運用の方法によって異なりそう。
時系列+もう一つの何らかの属性で検索することがほとんどのデータは、S3に置いてs3 selectが有用
この記事が話題になってて( なお元記事はここっぽい Moving from DynamoDB to tiered storage with MySQL+S3 | by Shane Hender | Zendesk Engineering )、かつて似たことやってAWS Summit Tokyoのミニステージで登壇したことがあったので忘れないうちに補足記事を書こうと思った。
PDFファイルの直接のリンクは以下になる。
ドローン運行監視 ーAmazon AthenaのBucketingとAmazon S3 Selectの活用ー
このスライドの概要としては、S3に保存されているドローンの飛行ログデータを、AthenaのCTAS + Bucketingを使って1日1回任意のまとまりでS3のファイルを適切な階層構造に配置しなおし、APIサーバーから取得できるようにしたという事例紹介となる。
この場合は飛行ログなので、機体のIDと、時系列で検索できればほとんどの場合十分だ。 このように時系列+何らかの属性のみで検索することがほとんどのデータがけっこうたくさんあると思っていて、そういうときにはこのパターンは適していると思う。
S3でファイルを置くならFirehoseとかで事足りそうに思うかもしれないが、Firehoseでファイルを置くとファイル数が増えてしまうので、そのままではS3 selectでクエリするのに適さないので、適切な粒度でファイルをまとめなおすという一手間を加えている。
このシステムでは実際にこの仕組みを使って、フロントエンドのNext.jsから、APIサーバーに任意の機体の任意の時間の飛行ログを問い合わせる仕組みを作って、過去の飛行履歴をすべて振り替えれるようにしている。
S3はExpress One Zoneもリリースされたので、より高速にレスポンスするようになっただろうから、S3はデータストレージとして更に有望になったと思う。システム要件によるが、REST APIなどでS3に置いたデータをクエリしてレスポンスするといったことは十分現実的だと思う。
最近はDuckDBに注目している。これもS3にあるログを短時間で読み込むことができる。
Obsidianのvimのjump to linkで英単語だけでなく日本語でもジャンプしたい
最近Obsidianを使って個人の知識データベースを作っている。日々学んだこと・見た記事などをdaily noteにしつつ、ストックとなる記事を貯めていっている。 これまでもEvernote・Notionや個人ブログ等で似たことをやろうとしていたが、Obsidianはローカルで使うmarkdownのエディタとして普通に日々のメモツール(これまではSlackとかにメモしていた)の延長で使えるので、メモとストックの垣根を無くせるのが良くて、常用するツールになりそう。
エディタを快適に使うときに自分にとって欠かせない要素なのがvimキーバインドで、Obsidianにも公式にvimキーバインドがあるので使っている。vimの設定はあまりカスタマイズしないほうだが、ジャンプ機能を使いたいと思って、
📗vim-easymotionのように入力した文字から始まる単語にジャンプしたい - Minerva
ここで知ったjump to linkを入れた。
普段はjump to anywhereをよく使うが、デフォルトの設定の正規表現\b\w{3,}\bだと日本語の位置にジャンプできない。そこで日本語でも兼用できる正規表現にしたいといくつか試行錯誤した結果、[。、,.\s]\S{4}で自分にとって満足のいく結果が出るようになった。