【AWS Batch】ジョブ定義のコマンドでDBの環境変数を使う時！

# 概要
AWS Batchのジョブではコマンドを記載して実行することができます。
DB接続情報の環境変数を使って、RDSへ繋ぐコマンドを作成していたのですが、何度かミスがあったので失敗パターンと成功パターンを記載します。

## 前提

以下が全て環境変数としてジョブ定義で設定されている前提です。
※コンソールからジョブ定義を作るときは手動入力できますし、CloudFormation使うときはテンプレートに直打ちするかSecret Managerから取ってくるなどの方法があります。

- RDS_HOST
- RDS_PORT
- RDS_USERNAME
- RDS_PASSWORD
- RDS_DB_NAME

上記のDB接続情報を利用して、`InsertCustomerProcedure`というあらかじめDBに登録しておいたプロシージャを呼び出します。

# 成功パターン

```
["sh","-c","mysql -h $RDS_HOST  -P $RDS_PORT -u $RDS_USERNAME -p$RDS_PASSWORD -D $RDS_DB_NAME -e \"CALL InsertCustomerProcedure();\""]
```

上記の場合、シェル (`sh`) を介して MySQL コマンドを実行し、環境変数が正しく解釈されています。
シェルを使用することで、環境変数が`$RDS_HOST`のように正しく評価され、その値がコマンドに渡されているんですね。

### SQLクエリも環境変数にする場合

環境変数`CALL_PROCEDURE`に`CALL InsertCustomerProcedure();`を定義しておけば、以下でも可能です。

```
["sh","-c","mysql -h $RDS_HOST -P $RDS_PORT -u $RDS_USERNAME -p$RDS_PASSWORD -D $RDS_DB_NAME -e \"$CALL_PROCEDURE\""]
```

無事に成功しました。
次に、失敗したパターンを紹介していきます。

## 失敗パターン1
こちらは失敗コマンド1つ目。

```
["mysql -h $RDS_HOST -P $RDS_PORT -u $RDS_USERNAME -p$RDS_PASSWORD -D $RDS_DB_NAME -e \"$CALL_PROCEDURE\""]
```

これで実行すると以下のエラーになりました。
CloudWatchのログにすら届かず、FAILEDになったジョブのコンテナの履歴に以下のように記載がありました。

```
CannotStartContainerError: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "mysql -h $RDS_HOST -P $RDS_PORT -u $RDS_USERNAME -p$RDS_PASSWORD -D $RDS_DB_NAME -e
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710373/e2e9c21e-812b-563c-07a0-46daa9198ede.png)

後でわかりましたが、上述の通り、コマンドを正しく実行するためには、シェルを起動する必要がありました。失敗した理由はおそらくそこにあると思われます。

## 失敗パターン2
こちらは失敗したパターン2つ目。
カンマ区切り？と思い、以下で実行。

```
["mysql", "-h", "$RDS_HOST", "-P", "$RDS_PORT", "-u", "$RDS_USERNAME", "-p$RDS_PASSWORD", "-D", "$RDS_DB_NAME", "-e", "CALL InsertCustomerProcedure();"]
```

これで実行すると以下のエラーになりました。
CWにはLogとして届いてます。

```
ERROR 2005 (HY000): Unknown MySQL server host '$RDS_HOST' (-2)
```

うーん、失敗。
後からわかりましたが、この場合もシェルを使用せずに直接 MySQL コマンドを実行していたことが原因でした。
この場合、環境変数が直接`$RDS_HOST`などとして解釈されず、そのまま文字列として扱われているよう。

MySQL クライアントが正しいホスト名やポート番号などを解決できず、エラーが発生したのです。

## 失敗したパターン3

カンマ区切りの記法、かつ、シェルを使用するとどうなるのか？

```
["sh","-c","mysql","-h","$RDS_HOST","-P","$RDS_PORT","-u","$RDS_USERNAME","-p$RDS_PASSWORD","-D","$RDS_DB_NAME","-e","CALL InsertCustomerProcedure();"]
```

CWのログにて、以下のエラーを確認。

```
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/run/mysqld/mysqld.sock' (2)
```

## 失敗したパターン4

MySQLクライアントが環境変数から値を取得する際に変数名内の`$`を誤って解釈してしまっていることが原因かも?と思い、特殊文字のエスケープに使用されるバックスラッシュ (`\`) をつけてみました。

これにより、`$RDS_HOST`などの各変数は正しく評価されるのか...?
いざ実行！

```
["mysql", "-h", "\\$RDS_HOST", "-P", "\\$RDS_PORT", "-u", "\\$RDS_USERNAME", "-p\\$RDS_PASSWORD", "-D", "\\$RDS_DB_NAME", "-e", "CALL InsertCustomerProcedure();"]
```

以下のエラーになりました。

```
Unknown suffix '\' used for variable 'port' (value '\$RDS_PORT')

mysql: Error while setting value '\$RDS_PORT' to 'port'
```

バックスラッシュを使ってもダメでした...。
ちなみにバックスラッシュ一つでやってもエラーになります。

## 失敗パターン5

```
["sh","-c","mysql -h $RDS_HOST -P $RDS_PORT -u $RDS_USERNAME -p$RDS_PASSWORD -D $RDS_DB_NAME -e \\\"$CALL_PROCEDURE\\\""]
```

これだと以下エラー。

```
ERROR 1049 (42000): Unknown database 'InsertCustomerProcedure();"'
```

DB名として扱われてしまった。
こちらの問題の原因も、コマンド文字列内でシェルエスケープが正しく処理されていないこと。

## 失敗パターン6

```
["sh","-c","mysql -h $RDS_HOST -P $RDS_PORT -u $RDS_USERNAME -p$RDS_PASSWORD -D $RDS_DB_NAME -e '$CALL_PROCEDURE'"]
```

これだと以下エラー。

```
ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '$CALL_PROCEDURE' at line 1
```

エラーメッセージ`"ERROR 1064 (42000)"`は、SQL構文エラー。

`-e`オプションを使用してMySQLコマンドを実行する場合、シングルクォーテーション内でSQLクエリを指定する必要がありますが、シングルクォーテーション内では環境変数が展開されないため、変数 `$CALL_PROCEDURE`が正しく展開されていないよう。

# 結論
ということで、色々と詰まりましたが、上述の成功パターンであればうまくいきました。
環境変数を直接コマンドに埋め込む場合、シェルを使って環境変数の値を評価・展開することが重要...ということを学びました。