はじめに
環境変数の扱いについて、戸惑いがありましたのでまとめます。
用途や仕様の背景が理解し切れて一ませんが、少なくとも現状はベストエフォートとは思えず、改善が進むと嬉しいです。
やっていたことの概要
- 言語:Rust(記事としては、Rustに依存する内容ではないです)
- 作っていたもの:AWS Lambda関数
- 動作確認の方法:後述(※)
- デプロイの方法:AWS SAM CLI(
sam deploy
) - その他:AWSの設定はCloudFormation(
template.yaml
)に集約
※動作確認の方法
AWS SAMを使うとローカルでLambda関数の動作確認が可能ですが、私の環境ではビルドが遅くデプロイ直前までは、cargo run
で動作確認を行っていました。
動作確認タイミング | 動作確認の例 | 動作確認環境 |
---|---|---|
開発時 |
cargo run , cargo test
|
MacOS |
デプロイ前検証 | sam local invoke |
カスタムランタイムのエミュレーションコンテナ |
デプロイ後検証 | AWSコンソールからLambdaトリガー実行 | Lambda上のカスタムランタイムのコンテナ(Zipでのデプロイのため) |
ここで問題なのは、動作確認のタイミングで全ての環境が異なる点です。
Rustに限らず環境変数は.env
ファイルを作成し何らかの手段でプログラム利用するのが多いかと思います。
ただデプロイ前検証、デプロイ後検証では、macに置いた.env
ファイルは何もしなければ持ち込まれません。
なお、環境設定ファイルなので環境ごとに設定するものを記載すべきかもしれませんが、GitHubに上げたくない隠匿したい情報を記載しています。環境ごとの違いはなく同じファイルを読んで欲しいのに読んでくれない・・・という問題に直面しました。
解決策
いくつか試しました。
- 解決策①ビルド時に
.env
をコピー対象にする - 解決策②
template.yaml
に環境変数をベタ書きする - 番外編
--env-vars
の利用 - 解決策③
--parameter-overrides
の利用 - 解決策④
samconfig.toml
にパラメータを定義する
sam local invokeで環境設定ファイルが読み込めない
まずは通常のRust開発を行なった状態から。
Rustにおける環境設定ファイルの利用は、dotenv
、once_cell
を使いました。
TEST=HelloEnv
AWS_S3_BUCKET=my-work-project-bucket
use serde::Deserialize;
use config::ConfigError;
use dotenv::dotenv;
use once_cell::sync::Lazy;
#[derive(Deserialize, Debug)]
pub struct Config {
pub test: String,
pub aws_s3_bucket: String,
}
// 初期化(定数へセット)
pub static CONFIG: Lazy<Config> = Lazy::new(||
{
dotenv().ok();
Config::from_env().unwrap()
}
);
// 環境変数を読み込む
impl Config {
pub fn from_env() -> Result<Self, ConfigError> {
let mut cfg = config::Config::new();
cfg.merge(config::Environment::new())?;
cfg.try_into()
}
}
mod tests {
use super::*;
#[test]
fn get_env() {
assert_eq!(CONFIG.test, "HelloEnv");
}
}
cargo
では、問題なく動作しています。
$ cd /work/project
$ cargo test
test config::config::tests::get_env ... ok
しかし、sam local invoke
では環境変数がなく構造体に展開できないエラーが出ます。
$ cd /work
$ sam build
$ sam local invoke {関数名}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: missing field `test`', src/config/config.rs:15:28
このコマンドを実行しているのは、MacOS上ですが、sam local invoke
ではコンテナが起動してその中で関数が実行されるため、.env
が参照できていません。
解決策①ビルド時に.env
をコピー対象にする
ビルドに利用しているMakefile内に1行追加。
build-StockRustFunction:
cargo build --bin stock --release --target x86_64-unknown-linux-musl
cp ./target/x86_64-unknown-linux-musl/release/stock $(ARTIFACTS_DIR)/bootstrap
cp ./.env $(ARTIFACTS_DIR)/.env # ★★今回追加★★
デプロイ前動作確認にて問題ないことを確認
$ cd /work
$ sam build
$ sam local invoke {関数名} // 成功
デプロイ後動作確認にて、環境変数という形で参照できないものの関数実行は成功することを確認。
これでいいんだ感。
いろいろ試しましたが、開発環境とデプロイデプロイ環境で同一の設定情報を使う限りにおいては、編集ファイルが少なく一番手っ取り早い手段でした。商用利用するサービスでは確実に開発と商用環境は異なると思いますのであくまで個人開発の範疇でしたら利用してもいいかもという感覚です。
Lambdaの「環境変数」で見えないため、デプロイ後時間が空いてしまうと設定していない情報がなぜか使えているという挙動が分かりづらい点も気になります。
解決策②template.yaml
に環境変数をベタ書きする
template.yaml
には、環境変数を定義する箇所があります。
これは、ビルド情報に含まれるためデプロイ前(sam local invoke
)環境、デプロイ後(sam deploy
)環境どちらでも有効に働きます。
Resources:
StockRustFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
TEST: HelloEnv
AWS_S3_BUCKET: my-work-project-bucket
デプロイ前動作確認にて問題ないことを確認
$ cd /work
$ sam build
$ sam local invoke {関数名} // 成功
デプロイ後動作確認にて、環境変数が設定されていることを確認。
なお、この画面上の「編集」ボタンにて追加削除も可能なので、template.yaml
を使わずともデプロイ後の関数に追加することも可能ですが、毎回手での追加作業が発生します。
開発環境は、.env
、デプロイ環境はtemplate.yaml
というわけで環境ごとにファイルが分けられているのも自然ですし、特に問題ない手段かと思いますが、template.yaml
にベタ書きなので設定する環境変数をまとめて管理したい場合や秘匿情報は別管理したい場合に不向きです。
番外編--env-vars
の利用
公式ドキュメントより、sam local invoke
の環境変数の持ち込みに使えそうなオプションは、--env-vars
と--parameter-overrides
があります。
まずは、リンク先に従い、--env-vars
から試してみます。
なお、後述しますが、こちらは、sam build
、sam deploy
のオプションとしては存在していないため、最終的には利用をしていません。
--env-vars PATH
のPATH先のファイルは、json
のみ指定可能で、なんと.env
は利用できないため、新たに作成します。
{
"StockRustFunction": {
"TEST": "HelloEnv!!",
"AWS_S3_BUCKET": "my-work-project-bucket"
}
}
Environment
部分を追加していますが、env.json
の定義に差し替えられる形になります。
Resources:
StockRustFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
TEST: TEST
AWS_S3_BUCKET: AWS_S3_BUCKET
デプロイ前動作確認にて問題ないことを確認
$ cd /work
$ sam build
$ sam local invoke --env-vars env.json {関数名} // 成功
次にデプロイ後の確認ですが、前述の通りsam build
、sam deploy
に--env-vars
は存在しませんので、デプロイ自体できませんでした。
$ sam deploy --env-vars env.json
Usage: sam deploy [OPTIONS]
Try 'sam deploy -h' for help.
Error: no such option: --env-vars
デプロイできる他の方法を模索します。
私の手順では、template.yaml
のenvironment
に任意の文字列を定義しましたが、本来はここにデプロイ後に使う値が設定されており、デプロイ前確認時(sam local invoke
)に別環境でテストする場合などに、上書きするような使い方が想定されているように思います。
ですので、用途が若干違うというか、今後sam build
やsam deploy
にも当該オプションがつくかというと難しいでしょう。
解決策③--parameter-overrides
の利用
--parameter-overrides
は、sam local invoke
、sam build
、sam deploy
全てに存在するオプションですのでこちらを利用します。
Parameters:
Test:
Type: String
AwsS3Bucket:
Type: String
Resources:
StockRustFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
TEST: !Ref Test
AWS_S3_BUCKET: !Ref AwsS3Bucket
デプロイ前動作確認にて問題ないことを確認
$ cd /work
$ sam build
$ sam local invoke {関数名} --parameter-overrides Test="HelloEnv" AwsS3Bucket="my-work-project-bucket" // 成功
デプロイ後動作確認にて問題ないことを確認
$ sam deploy --parameter-overrides Test="HelloEnv" AwsS3Bucket="my-work-project-bucket"
実行時のパラメータ指定が多いとだいぶ厳しいですね。
④でこのパラメータをなくしていきます。
解決策④samconfig.toml
にパラメータを定義する
//③と同じものを使うため、省略
こちらの公式の記載ルールに従い、実行時のパラメータを設定ファイルに記載していきます。
version = 0.1
[default]
[default.global.parameters]
parameter_overrides = "Test=HelloEnv AwsS3Bucket=my-work-project-bucket"
[default.deploy]
[default.deploy.parameters]
#parameter_overrides = "Test=HelloEnv AwsS3Bucket=my-work-project-bucket"
[default.local_invoke]
[default.local_invoke.parameters]
#parameter_overrides = "Test=HelloEnv AwsS3Bucket=my-work-project-bucket"
[default.deploy.parameters]
や[default.local_invoke.parameters]
にそれぞれ別の定義をすることもできます。
今回は、環境ごとの違いがないので、[default.global.parameters]
で共通設定しました。
デプロイ前動作確認にて問題ないことを確認。オプション指定も不要です。
$ cd /work
$ sam build
$ sam local invoke {関数名} // 成功
デプロイ後動作確認にて問題ないことを確認。オプション指定も不要です。
$ sam deploy
実行時のパラメータは略すことできましたが、samconfig.toml
に情報が混在してしまいました。
また、現在は2つの変数だけですが数が増えたらみにくくなります。
おわりに
現状は、④を使っていますが、一般的なのは③でしょうか。
結局同じ情報が.env
とsamconfig.toml
で二つあるのですが。。
参考
試せませんでしたが、S3にenv.json
をS3にアップしてその場所を参照するようにする方法も見かけました。
Environment:
Fn::Transform:
Name: AWS::Include
Parameters:
Location: s3://*******/environment.json