ユーザーデータでアプリを実行するためのシェルスクリプトの書き方
はじめに
こんにちは。DWS二人目の大島です。
EC2 をオートスケーリングなどで起動する際に、起動時にアプリケーションを起動してしまう、ように設定をしたいことがあると思います。
systemd などを使ってもいいのですが、DBのパスワード設定などを静的にしか行えないようなアプリケーションにおいては、パラメータストアなどから取得して設定ファイルを上書きするなどの処理が必要場合、シェルスクリプトを書いてユーザーデータで実行してしまうのが意外と楽だったりします。
しかし、ユーザーデータの実行ユーザーは root ユーザーなので、アプリ実行はアプリ実行ユーザーに切り替えて実行する必要があります。
なかなかこの辺りのシェルスクリプトの書き方などが混乱しがちなので、ユーザーの切り替えと変数の利用について、まとめました。
ユーザーデータの仕組み
まず簡単にユーザーデータの仕組みを見ておきます。
ユーザーデータ自体の仕組は非常にシンプルです。
cloud-init という EC2 を初期化するプロセスの中でシェルスクリプトとして実行されます。($0 は、実行されたスクリプトを示します)
先述の通り、ユーザーデータの実行ユーザーは root ユーザーなので、スクリプト上でファイルを作成などをすると、すべて root 権限となってしまうので注意してください。
# ユーザーデータで以下の内容を実行することで、確認ができるs
#!/bin/bash
echo $0 >> /home/ec2-user/log
pstree >> /home/ec2-user/log
# 実行結果
[ec2-user@ip-10-0-19-106 ~]$ cat log
/var/lib/cloud/instance/scripts/part-001
systemd-+-2*[(agetty)]
|-amazon-ssm-agen---7*[{amazon-ssm-agen}]
|-atd
|-auditd---{auditd}
|-chronyd
|-cloud-init-+-part-001---pstree
| `-tee
(略)
su を使ったユーザー切り替えパターン
su は指定したユーザーに切り替えるコマンドです。
su を使ったユーザーデータで最初にパッと思いつくのが↓のパターンです。
# スクリプト
su app-user
command1
command2
これはNGです。
シェルスクリプトを普段からよく使ってる人なら当たり前かもしれませんが、su
は新しいインタラクティブシェルを起動します。
そのため、su 以降の行のファイル上のコマンドは、シェルスクリプトを実行している元のシェル上で実行されます。
つまり、command1, 2 は root ユーザーとして実行されてしまいます。
su の man にもきっちり su
は新しいインタラクティブシェルを起動します、と記載されています。
When called with no user specified, su defaults to running an
interactive shell as root. When user is specified, additional arguments
can be supplied, in which case they are passed to the shell.
su -c オプション
su
には、新たに起動したインタラクティブシェルにコマンドを渡す -c
というオプションがあります。
こちらを実行することで、新しく起動したシェル上でコマンドを実行することができます。
# example
su ec2-user -c "echo 'hello' >> /home/ec2-user/log"
ここまでで、ユーザーを変更してコマンドを実行ができるようになりました。
次に変数の渡し方について見ていきましょう。
su における変数の利用
su は、設定した環境変数をリセットして、一部の環境変数(HOME, SHELL, USER, LOGNAME, PATH など)だけを設定するので、単に環境変数をセットするだけでなく、もう一手間加える必要があります。
su does: • clears all the environment variables except TERM and variables specified by --whitelist-environment • initializes the environment variables HOME, SHELL, USER, LOGNAME, and PATHman su より:https://man7.org/linux/man-pages/man1/su.1.html
変数の利用においては、いろいろパターンがあるとは思いますが、ここではざっくり3つ取り扱います。
- シェルスクリプト上の変数を利用する
- コマンド実行時に変数を渡す
- 環境変数を引き継ぐオプションを利用する
シェルスクリプト上の変数を利用する
一番簡単な方法はシェルスクリプト上の変数を利用してしまうことかと思います。
「"(ダブルクォート)」で文字列を囲った場合、その中にある変数はシェルスクリプト上で評価され展開されるので、変数の展開ができます。
# シェルスクリプト
MYENV=hello
su ec2-user -c "echo $MYENV"
# 結果
hello
ただ、一部起動時に環境変数として設定してやらなければならないものもあったりするので、その場合には、次項の起動時に環境変数として指定する方法と合わせる必要があります。
# シェルスクリプト
DB_PASSWORD=hogehoge
su - ec2-user -c "
DB_PASSWORD=$DB_PASSWORD start_some_app" # 環境変数にシェルスクリプト上の変数を展開して渡す
コマンド実行時に変数を引き渡す
コマンド実行時に、環境変数を指定することも可能です。
こちらは先ほどの例と違い、su -c で渡したコマンドの中で実際に変数を利用したい場合「'(シングルクォート)」でないと動作しません。
というのも、「"」だと即座に$MYENVの評価がなされるため、環境変数が設定される前の状態で、$MYENVが評価されます。(「”」の場合、「\」などでエスケープが必要です。)
# コマンド
MYENV=hello su ec2-user -c 'echo $MYENV'
# 結果
hello
環境変数を引き継ぐオプション(-m)を利用する
su には、現在のシェルの環境変数を引き継ぐための -m というオプションがあります。
こちらのオプションを利用すれば、現在のシェル上で普通に環境変数を宣言して利用できるので、実行可能です。
Preserve the entire environment, i.e., do not set HOME, SHELL, USER or LOGNAME. This option is ignored if the option --login is specified.man su より:https://man7.org/linux/man-pages/man1/su.1.html
# コマンド
export MYENV=hello
su ec2-user -m -c 'echo $MYENV'
# 結果
hello
sudo を利用するパターン
sudo では、su とは違い、そのまま実行したシェルでコマンドが実行されるので、シンプルです。
さらに sudo で変数を利用してコマンドを実行する場合、「"」や「'」が利用できるわけではなく直接記述することになり、sudo が実行される環境での環境変数がそのまま利用されます。
裏側では sudo はセキュリティの関係上、環境変数をリセットしてしまうので、注意は必要ですが、あまり意識せずに利用が可能かと思います。(必要であれば、-E オプションを使うことで、引き継ぐこともできます)
# 以下のように記述する場合、変数は sudo 実行時の環境で評価されるため、
# コマンド
export MYENV=hello
sudo echo $MYENV
# 結果
hello
# 以下のように環境変数自体はリセットされるため、env などを実行すると表示されない
# コマンド
export MYENV=hello
sudo env | grep MYENV
# 結果は何も表示されない
# -E オプションを使うと環境変数を引き継げる
# コマンド
export MYENV=hello
sudo -E env | grep MYENV
# 結果
MYENV=hello
おわりに
簡単にですが、ユーザー変更を伴うユーザーデータ実行について記述させていただきました。
どなたかの役に立てば幸いです。