AWS Systems Manager Parameter Store の値を systemd の環境変数に設定する

全般

変数展開がされないやんけ・・・!

TL;DR

  1. 値をローテしない場合は Parameter Store がシンプルで使いやすい。
  2. systemd の EnvironmentFile は変数やコマンドは展開されない。
  3. 事前に systemctl set-environment で値を設定することが可能。

はじめに

あけましておめでとうございます。
新年一本目の執筆権をいただきました Bell です。

今回は AWS Systems Manager の Parameter Store という機能を利用して
環境ごとの設定値差分を吸収しようとした際のお話です。

ことの発端

お昼休憩を終えたある日の午後

Bell: ゴクゴク… (食後に黒烏龍茶飲んでるからお昼ごはんの脂肪分は帳消しだな)

長田: ベルくん, ちょっと相談してもいいかな?

Bell: はいはい, なんでしょう?

長田: AWS 上の Linux で環境変数を使って本番と検証で DB の接続先とか, 外部連携の鍵とか切り替えたいんだけど・・・
なんかいい方法知らないかな?

Bell: そうですね・・・ Parameter Store ってのがあるのでそれでどうですかね?

ということで, AWS Systems Manager の Parameter Store の使い方をまとめます。

Parameter Store から値の取得

まずは parameter store から値を取得するまでを検証しましょう。
構成としては以下のイメージです。

EC2 インスタンス の aws cli を使って Parameter Store から値を取得し環境変数に設定します。
設定した環境変数を読み込んでアプリケーションが起動します。
シンプルですね。

ここでは DB のユーザ名とパスワードを取得する場合を想定して実践します。

値の設定

まずは Parameter Store に値を設定しましょう。
マネジメントコンソールから Systems Manager (ssm) を検索して画面を表示します。

左メニューのパラメータストアからパラメータの設定画面を表示します。

パラメータの作成ボタンから登録に進みます。

パラメータの設定をしていきます.

keyval
名前dev.User
説明db user name
利用枠標準
タイプ文字列
データ型text
dev-ssm-ps-db-user

パラメータを作成で確定してもう一度パラメータの作成ボタンからパスワード情報を登録します。

keyval
名前dev.Password
説明db user password
利用枠標準
タイプ文字列
データ型text
dev-ssm-ps-db-password

パラメータの名前には 環境の識別や IAM policy でのアクセス制御のため環境毎のプレフィックスを付与しました。

これで Parameter Store の設定は完了です。 直感的でいいですね。

EC2 インスタンスの作成

値を取得する EC2 インスタンスを作成します。
Parameter Store にアクセスするための権限が必要ですので、
まずは IAM Role とそれに適用する IAM Policy を作成しましょう。

マネジメントコンソールから IAM を検索して画面を表示し左メニューのロールを選択します。

EC2 インスタンスに付与するロールを作成したいので, ロールからロールを作成を選択します。

EC2 に付与するロールのため、ユースケースの選択には EC2 を選んで 次のステップを選択します。

権限を細かく制御したいのでポリシーの作成から新しくポリシーを作成します。

新しいポリシーに ec2:descrive-instancessm:GetParameters の権限を付与します。
これで Systems Manger に設定した秘匿情報にアクセスすることができるようになります。
パラメータ個別にアクセス制限を設定することも可能なので、本番と開発で参照の可否を分けることもできます。
今回は dev. で始めるパラメータの参照権限を付与しています。
インスタンス自身の Name タグのプレフィックスを取得するために ec2:descrive-instance も付与しています。

ec2:descrive-instance

項目
サービスEC2
アクションDescribeInstances

ssm:GetParameters

項目
サービスSystems Manager
アクションGetParameters
リソース Regionap-northeast-1
リソース Accountそのまま
リソース Parameter name…dev.*

設定できたら次のステップに進みましょう。
タグは Name タグに dev-ssm-param-policy とします。

ポリシーの確認画面の名前も同様に dev-ssm-param-policy を設定します。

作成できましたらロールの作成画面に戻り、再読み込みをしてから dev-ssm-param-policy をチェックして次のステップを選択します。

Name タグに dev-ssm-param-role を設定して次のステップを選択します。

ロール名にも dev-ssm-param-role を設定してロールの作成をします。

ロールができたら EC2 インスタンスを作成します。
ネットワークなどのリソースは必要に応じてよしなに作成してください。

EC2 を検索してインスタンスの画面を表示します。

新しくインスタンスを作成するのでインスタンスを起動

基本的にデフォルトや無料枠の設定で大丈夫です。
インスタンスの詳細の設定の IAM ロールに先程作成した dev-ssm-param-role を選択します。
Name タグには dev-ssm-ps-inst を設定します。

細かい設定はご自身のAWS環境に応じて設定してください。参考までに例で設定した内容をまとめて載せておきます。

内容に問題が無いことこ確認して作成しましょう。
インスタンスの状態が 実行中 になれば作成完了です。

インスタンスに SSH 接続して値が取れるか確認します。

aws ssm get-parameters --region ap-northeast-1 --name dev.User --query "Parameters[0].Value" --output text

parameter store に設定した値が取得できていればインスタンスの作成が完了です。

systemd の設定

作成したインスタンスの systemd に parameter store の値を読み込んでサービスを起動する設定を入れましょう。

追加する設定およびスクリプトは以下の 3 つです。

  1. ssm-param.sh
    • 起動するサービスを想定したスクリプト
    • DBのユーザ名とパスワードを一定間隔でログに出力する
  2. ssm-param-pre-exec.sh
    • parameter store から値を読み込んで設定するスクリプト
    • ssm-param.sh の前に起動する
  3. ssm-param.service
    • ssm-param.sh を起動するための systemd の定義ファイル

内容は以下のとおりです。

#!/bin/bash

LOG_FILE=$(dirname $0)/$(basename $0 .sh).log

while true; do
    echo "Connection DB by ${DB_USER}/${DB_PASSWORD}" >> $LOG_FILE
    sleep 10
done;

10 秒ごとにユーザ名とパスワードを出力しています。
DB 接続のヘルスチェックみたいなイメージですね。
スクリプトと同じ場所にログファイルを作成して追記しています。

#!/bin/bash

# region の設定
export AWS_DEFAULT_REGION=$(/usr/bin/curl 169.254.169.254/latest/meta-data/placement/region 2>/dev/null)

# インスタンス情報の取得
INSTANCE_ID=$(/usr/bin/curl 169.254.169.254/latest/meta-data/instance-id 2>/dev/null)
INSTANCE_NAME=$(/usr/bin/aws ec2 describe-instances --instance-ids ${INSTANCE_ID} --query "Reservations[0].Instances[0].Tags[?Key=='Name'].Value" --output text)

# パラメータの取得
DB_USER=$(/usr/bin/aws ssm get-parameters --name ${INSTANCE_NAME%%-*}.User --query "Parameters[0].Value" --output text)
DB_PASSWORD=$(/usr/bin/aws ssm get-parameters --name ${INSTANCE_NAME%%-*}.Password --query "Parameters[0].Value" --output text)

# パラメータを systemd に設定
systemctl set-environment DB_USER=${DB_USER}
systemctl set-environment DB_PASSWORD=${DB_PASSWORD}

aws cli を使って parameter store から設定値を取得して systemctl コマンドを利用し設定しています。
設定値の取得のためにリージョンの情報やインスタンスの情報が必要なのではじめに取得しています。

[Unit]
Description = load environment from aws system manager parameter strore

[Service]
User=root

ExecStartPre=/usr/bin/bash /opt/ssm-param-pre-exec.sh
ExecStart=/usr/bin/bash /opt/ssm-param.sh

ExecStartPre で値を設定して ExecStart でスクリプトを起動します。
シンプルですね。

設定が終わったら起動してログファイルを見てみましょう。

sudo systemctl start ssm-param

tail -f /opt/ssm-param.log

parameter store に保存された値が読み込めているのが確認できるかと思います。

失敗談: EnvironmentFile の設定

systemd の設定に環境変数を読み込むための EnvironmentFile が設定できるのですが、
こちらの挙動でハマったことがありました。

EnvironmentFile は変数の展開がされないのです!!

試しにサービスの設定を以下のように修正してみましょう。

[Unit]
Description = load environment from aws system manager parameter strore

[Service]
User=root

EnvironmentFile=/opt/ssm-param-pre-exec.sh
ExecStart=/usr/bin/bash /opt/ssm-param.sh

サービスを再起動して環境変数の値を確認してみましょう。

変数にコマンドが出力されているのがわかるでしょうか?
このように EnvironmentFile では変数やコマンドが展開されないので注意しましょう・・・💧

コメント