SREチームの長田です。 今回はssmwrapという拙作CLIツールのはなしです。
ssmwrapとは
ssmwrapは、AWS Systems Manager Parameter Store(以下SSM Params)から値を取得し、 環境変数またはファイルに出力した上でコマンドを実行するツールです。
secret類をSSM Paramsに保存している場合、アプリケーション実行時にSSM Paramsから必要な値を取得することになります。 AWSのサービスにアクセスするという操作は、それなりに手間がかかるものですが、 ssmwrapを使えば環境変数とファイルというより簡便な入出力インターフェイスを通してSSM Paramsの値を参照できます。 実装が簡潔になるだけでなく、アプリケーションからのAWS APIへの依存を排除することにもなります。
# SSM Paramsにこんな値が保存されている場合に、 $ aws secretsmanager get-secret-value \ --secret-id test/foo \ --query 'SecretString' "secret value" $ ssmwrap {"test/foo" を環境変数 "FOO" に出力するオプションを指定して} \ -- \ printenv FOO # コマンドを実行すると、 secret value # 環境変数経由で参照できる!
AWS APIへの依存排除は、ローカル環境やCI環境など、AWSにアクセスしづらく、かつダミーのsecretで事足りる場合にも便利です。 ダミーの(平文で保存しても問題ない)値を環境変数やファイルに書き出して、アプリケーションからはそれを参照すればいいわけです。
この度、このssmwrapが メジャーバージョンアップしv2 になりました *1。
何が変わったのか
主な変更点は以下の4つです。
- aws-sdk-go から aws-sdk-go-v2 への移行
- CLIインターフェイスの刷新
- ライブラリインターフェイスの変更
- パフォーマンス向上
それぞれ詳しく見ていきます。
aws-sdk-go から aws-sdk-go-v2 への移行
sssmwrapのv2化に着手したきっかけは、aws-sdk-go v1のサポート終了アナウンスでした。
ssmwrapもgoで書かれたAWSを利用するツールということで、aws-sdk-goを利用しており、 今回aws-sdk-go-v2に移行しました。
aws-sdk-go-v2の各関数は context.Context
を要求するので、
*2
ssmwrapのライブラリインターフェイスも変わることになります。
semantic versioningに則るなら、インターフェイスの変更はメジャーバージョンアップを伴わないとなー v2に上げるなら他のインターフェイスも整理したいなー ということでCLIのインターフェイスも刷新することにしました。
CLIインターフェイスの刷新
ssmwrap v1のCLIとしての使用例は、例えば以下のようなものでした。
# ssmwrap v1 $ ssmwrap \ -paths /foo/ \ -prefix FOO_ \ -file 'Name=/foo/CERT,Path=/path/to/foo.crt,Mode=600' \ -file 'Name=/foo/KEY,Path=/path/to/foo.key,Mode=600' \ -- app run
このフラグの意味は以下のとおりです。
-paths /foo/ -prefix SSMWRAP_
/foo/
直下にあるパラメータを取得し、 各パラメータ名にプレフィックスFOO_
をつけて環境変数にセットする
-file Name=/foo/CERT,Path=/path/to/foo.crt,Mode=600
/foo/CERT
の値を取得し、/path/to/foo.crt
に書き出す。 ファイルのパーミッションは600
とする
-file Name=/foo/KEY,Path=/path/to/foo.key,Mode=600
- 同上
-- app run
- コマンド
app run
を実行する。実行時にssmwrapがセットした環境変数を参照できる
- コマンド
はじめは環境変数への出力しか想定していなかったところに、 ファイルへの出力機能を追加したため、統一感のないインターフェイスになっていました。 そのため、
-prefix
は環境変数出力専用だが、ファイル出力にも効くように見える、- 環境変数として出力する対象のSSM Paramを指定するオプション
-paths
と ファイル出力先指定である-file
内のPath
が同じ単語なので紛らわしい
などの問題を感じていました。
これをssmwrap v2仕様に書き換えると以下のようになります。
# ssmwrap v2 $ ssmwrap \ -rule 'type=env,path=/foo/*,prefix=FOO_' \ -rule 'type=file,path=/foo/CERT,to=/path/to/foo.crt,mode=600' \ -rule 'type=file,path=/foo/KEY,to=/path/to/foo.key,mode=600' \ -- app run
-env
-file
フラグが -rule
に統一されました。
SSM Paramsの対象パラメータを指定する方法も以下のように変更されいます。
対象パラメータ | v1 | v2 |
---|---|---|
/foo/value という名前のパラメータのみを処理対象とする |
-names /foo/value |
-rule 'type=...,path=/foo/value,...' |
/foo/ 直下にあるパラメータのみを処理対象とする |
-paths /foo/ |
-rule 'type=...,path=/foo/*,...' |
/foo/ 以下にある全てのパラメータを再帰的に探索し処理対象とする |
-paths /foo/ -recursive |
-rule 'type=...,path=/foo/**/*,...' |
このようにどのパターンも -rule
フラグのみで表現するようになりました。
これにより、 「/foo/
は直下のパラメータのみ、/bar/
は再帰的に全てのパラメータを処理対象とする」 といった
複雑なルールも表現できるようになりました。
また、ssmwrap v1における -env-prefix
および --env-entire-path
も -rule
フラグ内のパラメータとして指定するようになりました。
これにより、以下のように path
毎に異なるプレフィックスを付けることも可能になりました。
# ssmwrap v2 ssmwrap \ -rule 'type=env,path=/foo/*,prefix=FOO_' \ -rule 'type=env,path=/bar/*,prefix=BAR_' \ ...
なお、-rule
フラグのエイリアスとして -env
-file
を用意していますので、
これを使えば多少短く書くこともできます。
-rule を使う場合 |
-env -file を使う場合 |
---|---|
-rule 'type=env,...' |
-env '...' |
-rule 'type=file,...' |
-file '...' |
ライブラリインターフェイスの変更
ssmwrapは、ライブラリとして利用するためのインターフェイスも提供しています。
ssmwrap.Export を実行すると、 SSM Parameterから取得した値を指定したルールに従って環境変数にエクスポートします。 以降の処理でエクスポートされた環境変数を参照することができます。
こちらのインターフェイスについても、CLIインターフェイスにあわせる形で変更しています。
// ssmwrap v1 opts := ssmwrap.ExportOptions{ Paths: []string{"/foo"}, Prefix: "TEST_", } if err := ssmwrap.Export(opts); err != nil { fmt.Fprintf(os.Stderr, "failed to export parameters: %v", err) os.Exit(1) } // 環境変数を参照する処理...
// ssmwrap v2 ctx := context.Background() rules := []ssmwrap.ExportRule{ { Path: "/foo", Prefix: "TEST_", }, } if err := ssmwrap.Export(ctx, rules, ssmwrap.ExportOptions{}); err != nil { fmt.Fprintf(os.Stderr, "failed to export parameters: %v", err) os.Exit(1) } // 環境変数を参照する処理...
また、v2化にあたり、importパスも変更しています。ご注意ください。
// ssmwrap v1 import "github.com/handlename/ssmwrap"
// ssmwrap v2 import "github.com/handlename/ssmwrap/v2"
パフォーマンス向上
ssmwrap v1では、例えば以下のようなフラグを渡した場合、AWSへのAPIリクエストが3回発生していました。
# ssmwrap v1 $ ssmwrap \ -paths /foo/ \ <-- GetParametersByPath -prefix FOO_ \ -recursive \ -file 'Name=/foo/CERT,Path=/path/to/foo.crt,Mode=600' \ <-- GetParameter -file 'Name=/foo/KEY,Path=/path/to/foo.key,Mode=600' \ <-- GetParameter -- app run
ssmwrap v2では重複する範囲のSSM Parameterの取得は省略されるため、AWSへのAPIリクエスト回数が減少しています。
先の ssmwrap v1 の例と同等の処理を ssmwrap v2 で行う場合のAPIリクエストは 1回 で済みます
(/foo/*
が /foo/CERT
/foo/KEY
を含んでいるため)。
# ssmwrap v2 $ ssmwrap \ -rule 'type=env,path=/foo/*,prefix=FOO_' \ <-- GetParametersByPath -rule 'type=file,path=/foo/CERT,to=/path/to/foo.crt,mode=600' \ <-- リクエストは発生しない -rule 'type=file,path=/foo/KEY,to=/path/to/foo.key,mode=600' \ <-- リクエストは発生しない -- app run
APIクエストが少ない分単純に速いですし、スループット制限 *3 にもかかりにくくなります。
おわりに
ssmwrapは、もともとはEC2上で動かすアプリケーションからSSM Paramsを参照するために作られたツールでした。
現在はアプリケーションの動作にはECSを使うことが主流になっています。
ECSであれば、Task Definitionに secrets
パラメータを設定する
*4
だけで、コンテナ内から環境変数として SSM Params の値を参照できるので、ssmwrapを使う機会は減ることになりました。
しかし、Lambda functionから手軽にSSM Paramsを参照する場合 *5 や、GitHub ActionsなどのCI/CDサービスでの利用、開発環境セットアップ時の開発用証明書の配置など、まだ活用できる場面はありそうだということで、今回v2化を行うことにしました。
他にもお役に立てるユースケースがあれば幸いです。
*2:V2 AWS SDK for Go adds Context to API operations | AWS Developer Tools Blog https://aws.amazon.com/jp/blogs/developer/v2-aws-sdk-for-go-adds-context-to-api-operations/
*3:記事執筆時点のスループット(1秒あたりのトランザクション数)は、 デフォルトでは40、 高スループット時はGetParameter=10,000 GetParameters=1,000 GetParameterByPath=100 となっています。詳細はAWSのドキュメント AWS Systems Manager エンドポイントとクォータ を参照してください。
*4:AWSのドキュメントAmazon ECS 環境変数を使用して Systems Manager パラメータを取得するを参照ください。
*5:Lambda exetension経由で参照することはできますが、 構成が少々煩雑になりますし、ローカル等Lambda以外の環境で動作させる際にはコードの分岐が必要になります