でこてっくろぐ ねお

でこてっくろぐ(http://dekokun.github.io/)から進化しました。でこらいふろぐ(http://dekolife.hatenablog.com/)の姉妹版。デコテックログ(deko tech log)である

JSONが格納された環境変数から、JSONのキーの名前とその値で環境変数をセットするツールを作った

2ヶ月くらいかけて毎日盆栽のようにちょっとずつ手を入れていたツールがまぁいい感じになってきたかなと判断したので、紹介エントリです。

2020/11/07 追記 このエントリを書いた2日後に、以下AWSの新機能が出て、以下私がこのツールを作った理由として述べている部分は、AWSの機能で代替可能となりました。まぁ、このエントリを読むと分かる通り、AWSがそこを実装していることを前提として、"捨てやすくする"という方針で開発したツールではあるので狙いどおりではあるんですが、ちょっと悔しい気持ちもありますね。捨てやすくする以前に使わなくてよくなった、的な。 AWS Fargate for Amazon ECS launches features focused on configuration and metrics

CDKにもこの機能が来ましたので、もうなんでもできます。ecs: secret JSON field for Fargate tasks

github.com

どんなツール?

github.com

以下のように、JSONが格納された環境変数があった時に、その環境変数名と、JSON内のキーからexportしたいキー名を指定すると、そのキー名で環境変数を設定しつつコマンド実行するツールです。

言葉では意味がわからないと思いますが以下コマンド例をどうぞ。

#  export VARS='{"examplekey1":"value1", "examplekey2":"value2", "examplekey3":"value3"}' のようなJSON形式の文字列を格納する環境変数が設定されている状態で以下を叩く
$ json2env --keys "examplekey1,examplekey2" --envname VARS env | grep examplekey
examplekey1=value1
examplekey2=value2

JSONの中から環境変数を設定してenvコマンドを叩いているので、envコマンドの出力としてJSONの中の値が出ています。

どういうシーンで使うことを想定しているの? なんでキー名を指定する必要があるの?面倒じゃない? とか JSONは標準入力から受け取ったほうが汎用性が高いんじゃない? 等の疑問が湧いてくるような独特なインターフェースだと思いますがそのような設計にした理由は以下に記載しています。

なんで作ったの?

ある日、AWSのECSに複数の秘密情報を環境変数で渡したい、という要件がありました。まぁよくある話ですよね。DBへの接続時のパスワードとかなんらかのaccess keyとか。

それを実現するためには、Secret ManagerのJSONを格納する機能を使ってJSONの辞書に複数キー入れて、ECSの機密情報を展開する機能でそのキー群を突っ込めばいいじゃんってなったんですが、https://docs.aws.amazon.com/AmazonECS/latest/userguide/specifying-sensitive-data-secrets.html#secrets-considerations Specifying sensitive data using Secrets Manager - Amazon ECS を見る限り、Fargateではsecretsとしてsecret managerの値(JSON)そのままは突っ込めるけど、JSONの中のキー指定をして環境変数に設定することはできないらしい、ということがわかりました。つまり、秘密情報として渡されたJSONの値をアプリケーション側で分解して使う必要があります。

Secret Managerとよく対比されるサービスであるParameter Storeを使って一つ一つ秘密情報を格納したり、Secret Managerを使う場合でもJSONを使わず1つ1つ秘密情報を格納すればいいという話はありつつ、私はSecret Manager の1つのSecretにJSONで複数の秘密情報を格納したかった*1ので、以下あるように、jqコマンドを使ってそのままJSON環境変数に変換するようなものを検索してまずは使い始めました。

stackoverflow.com

ただ、上記のようにシェルスクリプトをかく方法だと以下のような問題が感じられました。

  • パッと見で何やってるかよくわからないコードを、今後同じような要件が発生した際にコピペで増殖させていく必要がある
  • JSONを読んで何か環境変数を設定しているということは分かっても、どういう名前の環境変数を使ってるかが分からない
    • Secret Managerのキー指定での環境変数設定機能は今後Fargateでも対応されるのはほぼ間違いないと思っているため、取り外しが簡単なものにしたいが、取り外す際になんの環境変数を使っているかわからないとパッと見取り外しが心理的に面倒になってしまうだろうな、という思いがあった
    • もちろん上記jqのスクリプトを更にいじるとそのようなことはできるが、色々な箇所でコピペコードを少しずつ編集して使っていく、みたいなことをすると取り外しや今後機能追加したくなった際の障壁となるだろうという思いがあった

という話を同僚にしていたら、以下スライド中でfujiwaraさんが述べているような、"隙間家具"的なものが必要なのでは?という提案を受け、作り始めることとした。

speakerdeck.com

今回のツールは、上記発表資料に記載されている以下2点を強く意識して作ったものである。

設計思想

上記にも書いたが、このツールは結構独特の設計になっていて、ここではその理由を紹介します。最初は汎用性を重視してJSONを標準入力で受け取る方法を考えていたが、実際に、上記このツールを作った理由となったECSへの組み込みの用途で使おうとしたらイマイチ使い勝手が悪いなと思い、上記のようなインターフェースに変えました。

なお、JSONを標準入力で受け取ってコマンド実行するようなものを求めている場合は、以下ツールを使うといいと思います。 github.com

以下、このように設計した理由の詳細を記載します。

後で取り外しやすい設計

上記にも何度か記載していますが、Secret Managerに格納されたJSONから、JSONのキーを指定して環境変数経由でECSにわたす機能はすでにECS on EC2には存在するため、そのうちFargateにもこの機能が来ることはほぼ間違いないと感じています。そのため、その機能が実装された暁には、このツールは取り外す、という前提で作っていました。

そのために実際に取り外しやすいとはどのような状態か、と考えた時に以下が必要だと感じました。

  • 取り外す際の変更箇所が少ない(=導入時の変更箇所が少ない)
  • 取り外す際の心理的障壁が少ない

それぞれの要求に対してどのように設計したか、以下に記載していきます。

取り外す際の変更箇所が少ない(=導入時の変更箇所が少ない)設計

最初のインターフェースの設計は、以下のように、標準出力でJSONを受け取る、というものでした。

export SECRET = '{"hoge":"hoge", "fuga":"fuga"}'
echo $SECRET |  json2env --keys "hoge, fuga" env
hoge=hoge
fuga=fuga

しかしこの設計で実際にECSで動くツールに組み込もうとしたところ、Dockerfileで環境変数を展開し、更にパイプでjson2envにわたす必要があり、DockerfileのCMDをシェルを経由するなどして結構書き換えないといけない、ということに気づきました。

具体的な変更方法は、以下のような形でしょうか。

変更前

CMD ["somecommand"]

変更後

CMD ["sh", "-c", "echo $VARS | json2env --keys 'examplekey1,examplekey2' somecommand"]

もしくは、 echo $VARS | json2env --keys 'examplekey1,examplekey2' somecommand 相当のことをするwrapperを作ってそのスクリプトをCMDに書く、など。

実際にこのように変更すると、取り外す時の変更点が増えて心理的障壁が上がってしまうのではないか、という仮説のもと、以下のように最小限の変更で済むようなインターフェースにしました。

CMD ["json2env", "--keys", "examplekey1,examplekey2","--envname", "VARS", "somecommand"]

取り外す際の心理的障壁が少ない(取り外しによる影響が見えている)

上記の通り、今のインターフェースは、JSONの中のどのキーをexportするか明示的に指定する作りとしています。 最初に想定していた設計は、以下のように、特にkeysを指定しなくても動く、まぁ普通に考えるとこうなるよね、という設計でした。

export SECRET = '{"hoge":"hoge", "fuga":"fuga"}'
echo $SECRET |  json2env env
hoge=hoge
fuga=fuga

ただ、この設計だと将来このツール取り外す際に、問題が起きやすくなるだろうなと感じました。 実際に取り外す際の行動を想像すると、 以下のようなことになるかと思います

  • ECSで対象のキーを設定するように変更する
    • 上記コマンド例だと、それぞれhoge, fugaをECSの環境変数に設定するような設定を行う
  • ツールを取り外す

この際に、常に "exportされている環境変数は本当にこれだけなのか?何か見落としはないか?" というのが不安だろうな、と想像できたため、環境変数としてexportするkeyを明示的に指定することで、取り外す際に考えなくてはいけないことが明確になる、という設計にしました。

悩み

というように色々考えてツールを作ったわけですが、以下のような悩みがあります。

ツール名、もうちょっと付け方があるような気がする。今のjson2envは名前が広すぎるなぁ。 今は環境変数に格納されたJSONしか変換できないので、envjson2env、とか…?? とか悩んでいます。

また、そもそもこのツールは色々考えて作ったわけではありますが、考え過ぎでただの不格好なものができてしまっただけなのではないか、みたいな気持ちも少しあります。まぁ、実際に長い年月使ってみることでこのあたりははっきりしてくるのでしょう。

開発中の日々について

最近、仕事以外のありとあらゆることを1日にちょっとずつ、ただし毎日進めていくことで長い時間をかけて確実に進めていくというスタイルが好みなのですが、このツールの開発もそのような形で進めました。

また、このブログ自体もそのような形で書きました。

この手法は個人の趣味にはぴったりな手法で、少しずつでも完成に近づいていくのは気分が良いものです。"一旦終わらせる" ということさえできれば。このエントリは、このツールの開発を"一旦終わらせる"ということの宣言でもあります。こうしてまた新しいことに取り組んでいける。嬉しいことです。

謝辞

*1:複数の秘密情報を別々に管理すると設定が複数に散らばってしまうのでなんとなく嫌だったのです。Parameter Storeなら秘密情報を階層型に管理できるので、だいぶマシではあるんですが