PHP Slim 環境構築(13) AWSサービスの利用
Introduction
前回は、DockerコンテナにNginxとPHP-FPMの両方を格納した構成をEC2に構築し、同一サーバ上で動く別のDockerコンテナ(RDB, DynamoDB, S3)とアクセスを行いました。
今回は、RDB、DynamoDB、S3などをAWS上のサービスを利用するように変更します。
この一連のシリーズは、自分への備忘録が第一目的のため、だいぶ不親切です。
すみません・・・
AWS
SystemManager
DB接続情報などを格納するのにSystemManagerのパラメータストアを使用するようにします。
SecretsManagerによるシークレットローテーションなども試したいのですが、有償になってしまうため今回は断念しています(無償期間は過ぎた)。
今回使用したパラメータは以下の通りです。
キー名 | 意味 |
---|---|
DYNAMODB_ACCESS_KEY | DynamoDBにアクセスするIAMユーザーのAccessKey |
DYNAMODB_SECRET | DynamoDBにアクセスするIAMユーザーのSecret |
LOG_DB_PASSWORD | ログ用DBに想定しているRedshiftへの接続パスワード |
LOG_DB_USER | ログ用DBに想定しているRedshiftへの接続ユーザ |
STORAGE_ACCESS_KEY | S3にアクセスするIAMユーザのAccessKey |
STORAGE_BUCKET | アクセスするS3のバケット名 |
STORAGE_SECRET | S3にアクセスするIAMユーザのSecret |
USER_DB_PASSWORD | RDBへの接続パスワード |
USER_DB_USER | RDBへの接続ユーザ |
RDS(MariaDB)
今回は、テスト用に使用するRDBとして、RDS for MariaDBを使用しました。
Auroraを使用しないのは、もちろん無償だからです。
Dockerコンテナを起動しているEC2からアクセスできるように、セキュリティグループを設定する必要があります。
また、このDBへのアクセスユーザとパスワードをSSMに登録します(USER_DB_USER, USER_DB_PASSWORD)。
RDS(PostgreSQL)
今回は、ログ用DBとしてRedshiftを想定していますが、テスト用にはRDS for PostgreSQLを使用しました。
理由は、もちろん無償・・・。
セキュリティグループに注意してください。
くた、このDBへのアクセスユーザとパスワードをSSMに登録します (LOG_DB_USER, LOG_DB_PASSWORD)。
DynamoDB
DynamoDBとDynamoDBにアクセスすることができる(AmazonDynamoDBFullAccess)IAMユーザを作成しました。
このユーザのアクセスキーとシークレットを、SSMに登録します (DYNAMODB_ACCESS_KEY, DYNAMODB_SECRET)。
ElastiCache
ElastiCache(Redis)を作成しました。
S3
S3にテストに用いるバケットを作成し、そのバケットにアクセスできるIAMユーザを作成しました。
このユーザのアクセスキーとシークレット、および、作成したバケット名をSSMに登録します (STORAGE_ACCESS_KEY, STORAGE_SECRET, STORAGE_BUCKET)。
変更点
前回まではローカルPCで稼働するlocal環境、単一のEC2上にall-in-oneで動作するdevelopment環境を作成しました。
今回は、一部awsを利用するdevaws環境を新設しました。次回は、EC2をECSに移行する予定ですので、今回の環境は過渡期的なものです。
ソースツリー
今回の変更箇所です。
$(PROJECTROOT)
/compose
/web_front
devaws.conf (新規)
/web_hoge
Dockerfile
settings-devaws.yml (新規)
docker-compose-devaws.yml (新規)
/credentials
.empty (新規)
ssm
/src
/hoge
/lib
/Controller
DynamodbController.php
StorageController.php
ApplicationSetting.php (新規)
MyContainerBuilder.php
composer.json
composer.lock
.gitigore
/compose/web_front/devaws.conf (新規)
/compose/web_front/development.confと同じです。
/compose/web_hoge/Dockerfile
設定ファイルのコピー先を変更しました。
これは/var/www直下だと、通常ありそうな場所では無いため、分かりにくかったからです。
...(略)...
#COPY settings-${environment}.yml /var/www/settings.yml
COPY settings-${environment}.yml /usr/local/etc/myapp/settings.yml
...(略)...
/compose/web_hoge/settings-devaws.yml (新規)
前回までの設定ファイルsettings-development.ymlでは、接続情報を直接書いていたのですが、セキュリティ上、AWSサービスのアクセスキーなどを直接書くわけにはいかないので、SSMのパラメータストアの情報を見に行く仕組みを作成し、それに書き換えています。
また、SSMへの接続情報が追加されています ($$で囲まれた部分。ただし、SSMへアクセスできるIAMユーザのアクセスキーとシークレットは別ファイルで定義することにする)。
userdb:
host: mydbinstance.csvkmjl9r9hs.ap-northeast-1.rds.amazonaws.com
dbname: userdb
user: $$USER_DB_USER$$
password: $$USER_DB_PASSWORD$$
logdb:
host: logdbinstance.csvkmjl9r9hs.ap-northeast-1.rds.amazonaws.com
dbname: logdb
user: $$LOG_DB_USER$$
password: $$LOG_DB_PASSWORD$$
redis:
host: myredis.g3vevh.ng.0001.apne1.cache.amazonaws.com
dynamodb:
endpoint: https://dynamodb.ap-northeast-1.amazonaws.com
region: ap-northeast-1
key: $$DYNAMODB_ACCESS_KEY$$
secret: $$DYNAMODB_SECRET$$
table: mydynamo
storage:
endpoint: https://s3.ap-northeast-1.amazonaws.com
region: ap-northeast-1
key: $$STORAGE_ACCESS_KEY$$
secret: $$STORAGE_SECRET$$
bucket: $$STORAGE_BUCKET$$
ssm:
endpoint: https://ssm.ap-northeast-1.amazonaws.com
region: ap-northeast-1
/compose/docker-compose-devaws.yml (新規)
devaws環境用、docker-composeファイル。local, development環境は、docker-compose-common + 環境用と二つに分けていましたが、devaws環境は一つです。ややこしいので、docker-compose-commonは不要だったかもしれないですね。
web_hogeのvolumesに、ssm接続設定ファイルを含むディレクトリの定義が追加されています(../credentials:/usr/local/etc/myapp/credentials)。
version: '3'
services:
web_front:
build:
context: ./web_front
args:
- environment=devaws
ports:
- "80:3128"
- "443:8443"
volumes:
- ../certs:/etc/certs:ro
container_name: web_front
networks:
- private_net
web_hoge:
build:
context: ./web_hoge
args:
- environment=devaws
volumes:
- ../src/hoge:/var/www/hoge
- ../src/vendor:/var/www/vendor
- ../credentials:/usr/local/etc/myapp/credentials
container_name: web_hoge
networks:
- private_net
networks:
private_net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.28.0.0/24
/credentials/.empty
gitで空ディレクトリを管理するためのファイル。.gitkeepと同じです。
/credentials/ssm (新規、GitHubリポジトリには含まれていません)
SSM接続用のパラメータファイルです。
[default]
aws_access_key_id = AXXXXXXXXXXXXXXXXXXX
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
/src/hoge/lib/Controller/DynamodbController.php
DynamoDBの接続テーブルの設定の取得を、直接設定ファイルを参照するような形から、ApplicationSettingクラスに移譲するように変更しました。
class DynamodbController
{
/**
* @var string
*/
private $targetTable = null;
..(略)..
private function getTable(): string
{
if (is_null($this->targetTable)) {
$setting = ApplicationSetting::getInstance();
$this->targetTable = $setting->getSettingValue('dynamodb', 'table');
}
return $this->targetTable;
}
/src/hoge/lib/Controller/StorageController.php
DynamoDBControllerと同様に、設定ファイルを参照するのではなく、ApplicationSettingクラスに移譲するように変更しました。
/src/hoge/lib/ApplicationSettings.php (新規)
設定ファイルの内容を取得し、$$で囲まれた文字列があれば、実際の値をSSMに問い合わせて値を返します。
なお、値はローカル、apcuでキャッシュされます。
ソースは、前回にはMyContainerBuilderに含まれていた部分を抽出し、ssm関連処理を追加したものです。
<?php
namespace Hoge;
use Aws\Credentials\CredentialProvider;
use Aws\Exception\AwsException;
use Aws\Sdk;
/**
* Class ApplicationSetting
* @package Hoge
*/
class ApplicationSetting
{
/**
* 設定ファイルのパス
*/
const SETTINGS_YAML = '/usr/local/etc/myapp/settings.yml';
/**
* 設定ファイルのパス
*/
const SSM_CREDENTIALS_PROFILE = 'default';
const SSM_CREDENTIALS_PATH = '/usr/local/etc/myapp/credentials/ssm';
/**
* localにcacheするparameterのTTL
*/
const PARAMETER_TTL = 60;
/**
* @var ApplicationSetting
*/
private static $self = null;
/**
* @var array
* 取得済みの値
*/
private $replacedText = [];
/**
* @var array
* settings.yamlの内容
*/
private $settings = [];
/**
* @return ApplicationSetting インスタンスを取得
*/
public static function getInstance(): ApplicationSetting
{
if (is_null(static::$self)) {
static::$self = new static();
}
return static::$self;
}
/**
* ApplicationSetting constructor.
*/
protected function __construct()
{
$this->settings = yaml_parse_file(static::SETTINGS_YAML);
}
/**
* 指定されたカテゴリ内のキーの値を取得する
* @param string $category
* @param string $key
* @return mixed
*/
public function getSettingValue(string $category, string $key)
{
if (!isset($this->settings[$category]) || !isset($this->settings[$category][$key])) {
return false; // TODO: 本当はException
}
$value = $this->settings[$category][$key];
if (strpos($value, '$$') === false) {
return $value; // 置換の必要が無ければその値のまま返す
}
if (!preg_match('/^(.*)\$\$(.*)\$\$(.*)$/', $value, $matches)) { // {{KEY_NAME}}か?
return $value; // 置換の必要が無ければその値のまま返す
}
return $matches[1] . $this->replaceValue($matches[2]) . $matches[3];
}
/**
* @param string $original
* @return mixed
*/
private function replaceValue(string $original)
{
// このリクエストで置換済みの値か?
if (isset($this->replacedText[$original])) {
return $this->replacedText[$original];
}
// apcuに保存されている値か?
$fetchedValue = apcu_fetch($original);
if ($fetchedValue) {
$this->replacedText[$original] = $fetchedValue;
return $fetchedValue;
}
// SSMサービスへの認証
$provider = CredentialProvider::ini(
static::SSM_CREDENTIALS_PROFILE,
static::SSM_CREDENTIALS_PATH
);
$memoizedProvider = CredentialProvider::memoize($provider);
// SSM Clientの取得
$ssmSetting = $this->settings['ssm'];
$sdk = new Sdk([
'endpoint' => $ssmSetting['endpoint'],
'region' => $ssmSetting['region'],
'version' => '2014-11-06',
'credentials' => $memoizedProvider
]);
$ssm = $sdk->createSsm();
// settingsファイル内のすべての$$キーをリストアップ
$keys = [];
foreach ($this->settings as $settingArray) {
foreach ($settingArray as $key => $value) {
if (preg_match('/\$\$(.*)\$\$/', $value, $matches)) { // {{KEY_NAME}}か?
$keys[] = $matches[1];
}
}
}
// SystemManagerのパラメータストアから値を一斉に取得
try {
$result = $ssm->getParameters([
'Names' => $keys
]);
} catch (AwsException $e) {
return false; // TODO: 本当はException
}
foreach ($result['Parameters'] as $parameter) {
$name = $parameter['Name'];
$value = $parameter['Value'];
if (!apcu_exists($name)) {
apcu_store($name, $value, static::PARAMETER_TTL);
}
$this->replacedText[$name] = $value;
}
if (!isset($this->replacedText[$original])) {
return false; // TODO: 本当はException
}
return $this->replacedText[$original];
}
}
/src/hoge/lib/MyContainerBuilder.php
前回のソースから、ApplicationSettingで処理しているYamlファイル関連処理などを削除したものです。
/src/composer.json, composer.lock
composer require ext-apcuにより、APCuモジュールを有効にしました。
{
.. (略)..
"require": {
"ext-apcu": "^5.1"
.. (略)..
}
/.gitignore
credentialsディレクトリ処理を追加。
# Created by .ignore support plugin (hsz.mobi)
/.idea/
*.iml
/credentials/*
!/credentials/.empty
ここまでのソース
こちらでどうぞ。