【PHPバージョン共存】特定のバーチャルドメインのみPHPをバージョンアップする
Apache+php環境の複数のバーチャルドメインを運用されている環境で、特定のバーチャルドメインだけPHPのバージョンアップをしたいという相談を受ける機会があったので検証してみました。 php-fpmを利用してバーチャルドメインごとにphp-fpmのソケットを別にして稼働中の環境はさほど困ることはないかと思いますが、モジュール版でphpを稼働している環境は大変かなと思いましたので、モジュール版からphp-fpm(FastCGI版)へ変更するところ含めて記載します。
前提
以下の環境において、PHPをモジュール版からphp-fpm(Fast CGI)に変更し、「sub2.tekunote.com」のみPHP7.3へ変更します。「sub1.tekunote.com」はPHP5.4のままとします。なお、PHPはyumで導入することとします。 ◆前提構成概要
項目 | 内容 |
OS | CentOS Linux release 7.8 |
PHP | 5.4 (baseリポジトリ)、モジュール版で稼働 |
PHPモジュール版用設定先 | /etc/httpd/conf.d/php.conf |
バーチャルドメイン | sub1.tekunote.com,sub2.tekunote.com |
◆前提バーチャル設定詳細
<VirtualHost *:80> ServerName sub1.tekunote.com ServerAdmin root@localhost DocumentRoot /var/www/vhosts/sub1.tekunote.com/WWW ErrorLog /var/www/vhosts/sub1.tekunote.com/LOG/httpd/error_log CustomLog /var/www/vhosts/sub1.tekunote.com/LOG/httpd/access_log combined </VirtualHost> <VirtualHost *:80> ServerName sub2.tekunote.com ServerAdmin root@localhost DocumentRoot /var/www/vhosts/sub2.tekunote.com/WWW ErrorLog /var/www/vhosts/sub2.tekunote.com/LOG/httpd/error_log CustomLog /var/www/vhosts/sub2.tekunote.com/LOG/httpd/access_log combined </VirtualHost>
やってみた
php-fpm(5.4 base)のインストール
// インストール $ sudo yum install --enablerepo=base php-fpm // php-fpm自動起動 $ systemctl enable php-fpm.service
php73,php73-php-fpm(7.3 remi)のインストール
// remiリポジトリのインストール $ yum install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm // php73,php73-php-fpmのインストール $ yum install --enablerepo=remi-php73 php73 php73-php-fpm // php73-php-fpm自動起動 $ systemctl enable php73-php-fpm.service
php-fpm,php73-php-fpmデフォルト設定の退避
// 5.4系 $ cd /etc/php-fpm.d/ $ mv www.conf www.conf_org // 7.3系 $ cd /etc/opt/remi/php73/php-fpm.d/ $ mv www.conf www.conf_org
php-fpmの設定ファイルの作成
以下の通り、sub1.tekunote.com、sub2.tekunote.comの設定を作成します。 作成先はOS標準の5.4系は/etc/php-fpm.d/
、remiの7.3系は/etc/opt/remi/php73/php-fpm.d/
となります。 ◆sub1.tekunote.com用(5.4) /etc/php-fpm.d/sub1.tekunote.com.conf
[sub1.tekunote.com] listen = /var/run/php-fpm/sub1.tekunote.com.sock listen.allowed_clients = 127.0.0.1 listen.owner = apache listen.group = apache listen.mode = 0666 user = apache group = apache pm = ondemand pm.max_children = 50 pm.max_requests = 1000 slowlog = /var/www/vhosts/sub1.tekunote.com/LOG/php-fpm/sub1.tekunote.com-slow.log php_admin_value[error_log] = /var/www/vhosts/sub1.tekunote.com/LOG/php-fpm/sub1.tekunote.com-error.log php_admin_flag[log_errors] = on php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/session
◆sub2.tekunote.com用(7.3) /etc/opt/remi/php73/php-fpm.d/sub2.tekunote.com.conf
[sub2.tekunote.com] listen = /var/run/php-fpm/sub2.tekunote.com.sock listen.allowed_clients = 127.0.0.1 listen.owner = apache listen.group = apache listen.mode = 0666 user = apache group = apache pm = ondemand pm.max_children = 50 pm.max_requests = 1000 slowlog = /var/www/vhosts/sub2.tekunote.com/LOG/php-fpm/sub2.tekunote.com-slow.log php_admin_value[error_log] = /var/www/vhosts/sub2.tekunote.com/LOG/php-fpm/sub2.tekunote.com-error.log php_admin_flag[log_errors] = on php_value[session.save_handler] = files php_value[session.save_path] = /var/lib/php/session
ポイントはlisten = ソケット名です。listen
でバーチャルドメインごとに異なるソケットを指定することで、対応するバージョンへの接続を振り分けることができます。また、今回はソケットを採用しましたがSetHandler "proxy:fcgi://127.0.0.1:ポート番号"
のようにポート番号を個別に割り当てることでも振り分けができます。子プロセスの制御については、複数のドメインを1サーバに搭載する場合、子プロセスを必要に応じて立ち上げるpm = ondemand
が不要なリソースを抑えられるという判断で、ここではそのように設定しています。その他の設定についても必要最低限を記載していますので、環境に合わせて適宜修正ください。php-fpmの設定については下記をご参考ください。 ◆php-fpm設定の参考サイト PHP: 設定 - Manual
apacheのモジュール版設定の削除
$ cd /etc/httpd/conf.d $ mv php.conf php.conf_org
phpインストール時のデフォルトでは上記のファイルでモジュール版用の設定がされています。もし、個別に設定している場合は、該当の設定を削除してください。php.confの説明は割愛しますが、DirectoryIndex index.php
が必要な方は、この設定だけ残すか、移動させましょう。
apacheとphp-fpm,php73-php-fpmの連携
以下のようにバーチャルドメインの設定を行います。FilesMatch
ディレクティブで囲まれたところが追記する個所です。
<VirtualHost *:80> ServerName sub1.tekunote.com ServerAdmin root@localhost DocumentRoot /var/www/vhosts/sub1.tekunote.com/WWW ErrorLog /var/www/vhosts/sub1.tekunote.com/LOG/httpd/error_log CustomLog /var/www/vhosts/sub1.tekunote.com/LOG/httpd/access_log combined # php-fpm <FilesMatch \.php
gt; SetHandler "proxy:unix:/run/php-fpm/sub1.tekunote.com.sock|fcgi://localhost" </FilesMatch> </VirtualHost> <VirtualHost *:80> ServerName sub2.tekunote.com ServerAdmin root@localhost DocumentRoot /var/www/vhosts/sub2.tekunote.com/WWW ErrorLog /var/www/vhosts/sub2.tekunote.com/LOG/httpd/error_log CustomLog /var/www/vhosts/sub2.tekunote.com/LOG/httpd/access_log combined # php73-php-fpm <FilesMatch \.php
gt; SetHandler "proxy:unix:/run/php-fpm/sub2.tekunote.com.sock|fcgi://localhost" </FilesMatch> </VirtualHost>
php-fpm,php73-php-fpmの起動とapacheの設定反映
$ systemctl start php-fpm.service $ systemctl start php73-php-fpm.service $ systemctl reload httpd.service
動作確認
phpinfoで各バーチャルドメインのPHPバージョンの確認と、php-fpmで動作していることの確認を行いました。結果は以下の通りです。php-fpmで動作していることはServer API
の項目でFPM/FastCGI
となっていることで確認できます。もし、モジュール版で動作している際はApache 2.0 Handler
と表示されます。 ◆sub1.tekunoto.com ◆sub2.tekunoto.com
最後に
PHPのドメイン個別のバージョンアップの要望に柔軟に対応できそうです。また、Plesk等のサーバ管理ツールでは、ドメインごとにPHPのバージョンを容易に管理できますが、スケールアウトやパフォーマンスのチューニングのことを考えるとサーバ管理ツールを利用することがネックとなる場合もあるかと思います。そういったところからの移行の時にも便利だなと思いました。
【AWS Lambda for Ruby】eventパラメータの取り出し方
Cloudwatch Alarm
⇒SNSトピック
をLambdaの起動トリガとした際に、event
パラメータの取り出し方に手間取ってしまったのでここに残しておこうと筆をとりました。
やりたかったこと
以下の構成で、SNSトピックのイベントデータから、アラート対象のロードバランサおよびターゲットグループを特定したい。
前提のeventパラメータ
以下のテストイベントを利用します。実際のSNSトピックから渡されるeventパラメータと構成は同じです。伏せたいところを伏せただけです。
{ "Records": [ { "EventSource": "aws:sns", "EventVersion": "1.0", "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:event-test:abcdefghijklmnopqrstuvwxyz", "Sns": { "Type": "Notification", "MessageId": "abcdefghijklmnopqrstuvwxyz", "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:event-test", "Subject": "ALARM: \"alarm-event-test\" in Asia Pacific (Tokyo)", "Message": "{\"AlarmName\":\"alarm-event-test\",\"AlarmDescription\":null,\"AWSAccountId\":\"123456789012\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [0.0 (06/06/20 14:04:00)] was less than the threshold (1.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2020-06-06T14:05:17.378+0000\",\"Region\":\"Asia Pacific (Tokyo)\",\"AlarmArn\":\"arn:aws:cloudwatch:ap-northeast-1:123456789012:alarm:alarm-event-test\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"HealthyHostCount\",\"Namespace\":\"AWS/ApplicationELB\",\"StatisticType\":\"Statistic\",\"Statistic\":\"MINIMUM\",\"Unit\":null,\"Dimensions\":[{\"value\":\"targetgroup/tg-event-test/abcdefghijklmnopqrstuvwxyz\",\"name\":\"TargetGroup\"},{\"value\":\"ap-northeast-1c\",\"name\":\"AvailabilityZone\"},{\"value\":\"app/lb-test-event/abcdefghijklmnopqrstuvwxyz\",\"name\":\"LoadBalancer\"}],\"Period\":60,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"LessThanThreshold\",\"Threshold\":1.0,\"TreatMissingData\":\"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\":\"\"}}", "Timestamp": "2020-06-06T14:05:17.430Z", "SignatureVersion": "1", "Signature": "abcdefghijklmnopqrstuvwxyz", "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-abcdefghijklmnopqrstuvwxyz.pem", "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:123456789012:event-test:abcdefghijklmnopqrstuvwxyz", "MessageAttributes": {} } } ] }
eventパラメータの型の確認
目的のパラメータは、eventパラメータ内のMessage
の中に含まれています。まずは、Message
までの型を確認してみます。 型の確認 Message
はString扱いです。
def event_test(event:, context:) p event.class p event['Records'].class p event['Records'][0].class p event['Records'][0]['Sns'].class p event['Records'][0]['Sns']['Message'].class end => Hash => Array => Hash => Hash => String
MessageをHashへ変換して出力
def event_test(event:, context:) p JSON.parse(event['Records'][0]['Sns']['Message']) end
Messageの出力結果 大分スッキリMessage
の中身が確認できます。
{ "AlarmName": "alarm-event-test", "AlarmDescription": null, "AWSAccountId": "123456789012", "NewStateValue": "ALARM", "NewStateReason": "Threshold Crossed: 1 out of the last 1 datapoints [0.0 (06/06/20 14:04:00)] was less than the threshold (1.0) (minimum 1 datapoint for OK -> ALARM transition).", "StateChangeTime": "2020-06-06T14:05:17.378+0000", "Region": "Asia Pacific (Tokyo)", "AlarmArn": "arn:aws:cloudwatch:ap-northeast-1:123456789012:alarm:alarm-event-test", "OldStateValue": "OK", "Trigger": { "MetricName": "HealthyHostCount", "Namespace": "AWS/ApplicationELB", "StatisticType": "Statistic", "Statistic": "MINIMUM", "Unit": null, "Dimensions": [ { "value": "targetgroup/tg-event-test/abcdefghijklmnopqrstuvwxyz", "name": "TargetGroup" }, { "value": "ap-northeast-1c", "name": "AvailabilityZone" }, { "value": "app/lb-test-event/abcdefghijklmnopqrstuvwxyz", "name": "LoadBalancer" } ], "Period": 60, "EvaluationPeriods": 1, "ComparisonOperator": "LessThanThreshold", "Threshold": 1, "TreatMissingData": "- TreatMissingData: missing", "EvaluateLowSampleCountPercentile": "" } }
ロードバランサとターゲットグループ名の取り出し
def event_test(event:, context:) message = JSON.parse(event['Records'][0]['Sns']['Message']) p message['Trigger']['Dimensions'].select { |k| k['name'] == 'LoadBalancer' }[0]['value'] p message['Trigger']['Dimensions'].select { |k| k['name'] == 'TargetGroup' }[0]['value'] end => "app/lb-test-event/abcdefghijklmnopqrstuvwxyz" => "targetgroup/tg-event-test/abcdefghijklmnopqrstuvwxyz"
最後に
eventパラメータの取り出しでno implicit conversion of String into Integer
が発生した際に、ご参考いただければ幸いです。
【VSCode】bash: __git_ps1: command not foundが出たときの対処方法
git-prompt.sh
(gitのプロンプト表示のカスタマイズ用のシェル)、git-completion.bash
(gitコマンド補完用のシェル)を利用しているLinux環境で、VSCodeの結合ターミナル利用時に下記の通り『bash: __git_ps1: command not found』が表示されることがあったので、対象方法を調べてみました。なお、TerraTerm等のターミナルソフトでは、該当のbashのエラーは表示されないことを前提としています。 もし、TerraTerm等のターミナルソフトでも表示される場合は、__git_ps1
はgit-completion.bash
内で定義されているので、git-completion.bash
を読み込めていない、もしくは読み込む前に__git_ps1
を呼び出しているかのいずれかだと思います。
VSCodeの結合ターミナル上の原因
VSCodeのデフォルト設定では、結合ターミナルはログインシェルとしては動作せず、~/.bash_profile
も実行されない。 【参考】 Integrated terminal can't find __git_ps1 · Issue #9484 · microsoft/vscode · GitHub
対処
settings.json
に以下を設定します。terminal.integrated.shell.linux
は利用するシェルを指定します。デフォルトはbashなので、bashを利用する場合は省略可能です。terminal.integrated.shellArgs.linux
は、~/.bash_profile
を実行するための設定です。
"terminal.integrated.shell.linux": "/bin/bash", "terminal.integrated.shellArgs.linux": [ "-l" ]
【Key Management Service】Lambda Rubyで環境変数を暗号化する
KMS(Key Management Service)を利用して、Lambdaの環境変数の暗号化・復号化を試してみます。なお、利用するランタイムはRubyを利用します。
Lambdaの環境変数の暗号化・復号化をやってみよう
KMSのカスタマーマスターキーを作成
カスタマーマスターキーをCLIから作成し、エイリアス名も設定します。キーポリシーは今回はデフォルトで作成されたままのポリシーを使用します。
カスタマーマスターキーの作成
$ aws kms create-key { "KeyMetadata": { "Origin": "AWS_KMS", "KeyId": "<カスタマーマスターキーID>", "Description": "", "KeyManager": "CUSTOMER", "Enabled": true, "KeyUsage": "ENCRYPT_DECRYPT", "KeyState": "Enabled", "CreationDate": 1557209546.076, "Arn": "arn:aws:kms:ap-northeast-1:<AWSアカウントID>:key/<カスタマーマスターキーのキーID>", "AWSAccountId": "<AWSアカウントID>" } }
カスタマーマスターキーにエイリアスを設定
エイリアスはdemo-lambda
で設定しています。--target-key-id
には上で作成したカスタマーマスターキーのキーIDを指定します。
$ aws kms create-alias --alias-name alias/demo-lambda --target-key-id <カスタマーマスターキーのキーID>
Lambda関数へKMSの復号化権限を付与
Lambda関数が暗号化した環境変数を復号化できるようにロールにkms:Decrypt
権限を付与する必要があります。権限の追加に関しては、割愛します。ポリシー例は以下の通りです。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "DemoKms0", "Effect": "Allow", "Action": "kms:Decrypt", "Resource": "arn:aws:kms:ap-northeast-1:<AWSアカウントID>:key/<カスタマーマスターキーID>" } ] }
Lambda関数へ環境変数を設定
コンソール上から以下の通り環境変数を設定します。ここで登録した環境変数USER
とPASSWD
を暗号化した後、Lambda関数上で復号化します。
環境変数を暗号化
暗号化の設定のプルダウンを開きます。伝送中の暗号化のためのヘルパーの有効化にチェックを入れ、伝送中に暗号化する AWS KMS キーに、初めに作成したカスタマーマスターキーのARNを設定します。ARNは、AWSコンソールのKMSのページ等から確認できます。
環境変数USER
とPASSWD
の欄に暗号化のボタンが表示されるのでクリックし、暗号化します。下記は暗号化後の表示です。
Lambda関数の作成
暗号化された環境変数と、復号化した環境変数を表示する関数を作成してみます。
require 'aws-sdk' def demo_kms(event:, context:) kms_client = Aws::KMS::Client.new # 環境変数USERの復号化 user = kms_client.decrypt(ciphertext_blob: Base64.decode64(ENV['USER']))[:plaintext] # 環境変数PASSWDの復号化 passwd = kms_client.decrypt(ciphertext_blob: Base64.decode64(ENV['PASSWD']))[:plaintext] puts "USER(暗号化):#{ENV['USER']}" puts "PASSWD(暗号化):#{ENV['PASSWD']}" puts "USER(復号化):#{user}" puts "PASSWD(復号化):#{passwd}" end
実行結果
Lambdaのログ出力の結果です。暗号化した環境変数と復号化した環境変数が表示されました。
Lambdaの環境変数の暗号化・復号化まとめ
CloudFormationでLambdaを作成する際、AWS::Lambda::Function
リソースのKmsKeyArn
プロパティで環境変数の暗号化に利用するAWS Key Management Service キーは指定できるようです。ドキュメントはこちら。ただ、実際に環境変数を暗号化するところを、CloudFormationで対応するようなプロパティは見受けられませんでした。自動化して利用する際は、今回のように平文を登録してLambda上で暗号化するのではなく、暗号化済みのものをLambda上に登録する必要があるってことなのですかね。
【Serverless Framework】EC2インスタンス起動時に動的パブリックIPをLambda Rubyでroute53に登録
CloudWatch EventsのEC2インスタンスの起動をトリガーにLambda関数を実行し、起動したEC2インスタンスの動的パブリックIPをroute53のレコードに登録します。また、EC2の停止をトリガーに対象のレコードを削除します。 VPNが接続できない、したくない環境、Elastic IPを節約したいといような個人ユーザにはちょっぴりうれしいかもしれません。興味本位で以下を触りたかったというのが本音で、お題はおまけです。
- Lambdaでrubyを使ってみたい
- Serverless Frameworkを使ってみたい
前提条件
前提条件
- CentOS 7系利用
- credentialが設定済みであること
- Serverless Frameworkインストール済み
- ruby2.5(Lambdaのサポートバージョン)インストール済み
構成図
機能詳細
EC2インスタンスの起動時に以下の2つのタグを利用して、route53のホストゾーンにAレコードを登録します。SetRoute53タグの値がONの時、[Nameタグの値].[ドメイン名]で、動的パブリックIPをroute53のAレコードに登録します。また、EC2インスタンスの停止時に登録されているAレコードの削除を行います。
利用するタグ
- Nameタグ
- SetRoute53タグ(値にONを記載)
設定例
項目 | 設定値 |
Nameタグ | demo-machine |
SetRoute53タグ | ON ※その他の値やタグ未設定の場合は登録しない |
ドメイン名 | tekunote.com |
登録されるAレコードのFQDN | demo-machine.tekunote.com |
やってみよう
Serverless FrameworkでLambda関数の作成とデプロイ
Lambda関数の雛型作成
サービス名、作成先のパス共にdynamic-dns
でAWS Lambda のRuby環境の雛型を作成し、対象のパスへ移動します。
$ sls create --template aws-ruby --path dynamic-dns --name dynamic-dns $ cd dynamic-dns
dynamic-dns
ディレクトリ内に作成されたserverless.yml
、handler.rb
の2ファイル利用します。
$ ls -la total 16 drwxrwxr-x. 3 demo-user demo-user 83 May 9 17:04 . drwx------. 15 demo-user demo-user 4096 May 9 17:04 .. -rw-r--r--. 1 demo-user demo-user 1124 May 6 23:23 .gitignore -rw-rw-r--. 1 demo-user demo-user 1552 May 9 09:50 handler.rb drwxrwxr-x. 2 demo-user demo-user 157 May 9 16:00 .serverless -rw-r--r--. 1 demo-user demo-user 1011 May 9 15:56 serverless.yml
環境変数の設定
次の手順で、serverless.yml
を設定しますが、以下のパラメータはべた書きせず、環境変数から読み込むようにしていますので設定しておきます。
$ export AWS_ACCOUNT_ID=111122223333 # AWSアカウントID $ export HOSTED_ZONE_ID=ABCDEFH012345 # 設定対象のホストゾーンID $ export AWS_REGION=ap-northeast-1 # リージョン $ export DOMAIN=tekunote.com # Aレコードを設定する対象のドメイン名
serverless.yamlの設定
以下の通り設定します。環境変数HOSTED_ZONE_ID
、DOMAIN
についてはLambda関数の環境変数に登録されます。serverless.yml
のリファレンスはこちらです。
service: dynamic-dns provider: name: aws stage: nonstage runtime: ruby2.5 region: ${env:AWS_REGION} memorySize: 256 timeout: 60 iamRoleStatements: - Effect: Allow Action: - ec2:DescribeInstances Resource: "*" - Effect: Allow Action: - route53:ChangeResourceRecordSets - route53:ListResourceRecordSets Resource: "arn:aws:route53:::hostedzone/${self:functions.change_record.environment.HOSTED_ZONE_ID}" functions: change_record: handler: handler.change_record environment: HOSTED_ZONE_ID: ${env:HOSTED_ZONE_ID} DOMAIN: ${env:DOMAIN} events: - cloudwatchEvent: event: source: - "aws.ec2" detail-type: - "EC2 Instance State-change Notification" detail: state: - running - stopped
serverless.yaml
の設定内容について補足します。 Lambda関数に割り当てるIAMロールの設定はiamRoleStatements:
内で設定しています。Lambda関数にec2:DescribeInstances
、route53:ChangeResourceRecordSets
、route53:ListResourceRecordSets
の権限を設定しています。CloudWatch Logsの操作関連の権限も必要ですが、Serverless Frameworkではデフォルトで付与されるためserverless.yaml
内に記載する必要はありません。
iamRoleStatements: - Effect: Allow Action: - ec2:DescribeInstances Resource: "*" - Effect: Allow Action: - route53:ChangeResourceRecordSets - route53:ListResourceRecordSets Resource: "arn:aws:route53:::hostedzone/${self:functions.change_record.environment.HOSTED_ZONE_ID}"
CloudWatch Eventのトリガ(EC2起動時に実行)はevents:
内で設定しています。
events: - cloudwatchEvent: event: source: - "aws.ec2" detail-type: - "EC2 Instance State-change Notification" detail: state: - running - stopped
handler.rbの作成
今回作成してみたLambda関数のソースは以下です。handler.rb
に上書きします。
require 'aws-sdk' def change_record(event:, context:) ttl = '60' record_type = 'A' filter_tag_key = 'SetRoute53' filter_tag_value = 'ON' ec2_client = Aws::EC2::Client.new route53_client = Aws::Route53::Client.new # タグとCloudWatchのイベントから取得したインスタンスIDでフィルタ ec2_resp = ec2_client.describe_instances( filters: [ { name: "tag:#{filter_tag_key}", values: [filter_tag_value.to_s] } ], instance_ids: [(event['detail']['instance-id']).to_s] ) instance = ec2_resp.reservations[0].instances[0] instance.tags.each do |tag| # レコードの更新にはNameタグの値を利用するので、Nameタグと一致するときのみ処理 next unless tag[:key] == 'Name' route53_resp = route53_client.list_resource_record_sets( hosted_zone_id: (ENV['HOSTED_ZONE_ID']).to_s, start_record_type: record_type, start_record_name: "#{tag[:value]}.#{ENV['DOMAIN']}.", max_items: 1 ) record_name = route53_resp.resource_record_sets[0].name record_value = route53_resp.resource_record_sets[0].resource_records[0].value # 起動時は、EC2インスタンスに割り当てられたパブリックIPを利用して更新処理 if instance.state.name == 'running' record_set_action = 'UPSERT' ip_address = instance.public_ip_address # 停止時は、既にAレコードに登録済みのIPを取得し削除処理 elsif instance.state.name == 'stopped' record_set_action = 'DELETE' ip_address = record_name == "#{tag[:value]}.#{ENV['DOMAIN']}." ? record_value : nil end next if ip_address.nil? # Aレコードの更新、削除処理 route53_client.change_resource_record_sets( change_batch: { changes: [ action: record_set_action, resource_record_set: { name: "#{tag[:value]}.#{ENV['DOMAIN']}.", type: record_type, ttl: ttl.to_s, resource_records: [ { value: ip_address } ] } ] }, hosted_zone_id: ENV['HOSTED_ZONE_ID'] ) # CloudWatch Logに出力 puts "#{record_set_action} Record Infomation: type=#{record_type} fqdn=#{tag[:value]}.#{ENV['DOMAIN']}. ip=#{ip_address}" end end
デプロイ
CloudFormationスタックdynamic-dns-nonstage
が作成され、CloudFormationから各リソースが作成されます。deploy時のオプションの詳細表示(-v
)で、CloudFormationからどのリソースが作成されるか確認できますが、今回は指定せずに伏せています。
[demo-user@demo-machine dynamic-dns]$ sls deploy Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Creating Stack... Serverless: Checking Stack create progress... ..... Serverless: Stack create finished... Serverless: Uploading CloudFormation file to S3... Serverless: Uploading artifacts... Serverless: Uploading service dynamic-dns.zip file to S3 (1.92 KB)... Serverless: Validating template... Serverless: Updating Stack... Serverless: Checking Stack update progress... ..................... Serverless: Stack update finished... Service Information service: dynamic-dns stage: nonstage region: ap-northeast-1 stack: dynamic-dns-nonstage resources: 7 api keys: None endpoints: None functions: change_record: dynamic-dns-nonstage-change_record layers: None
動作確認
EC2インスタンスにタグを設定し起動
以下のようにEC2インスタンスのタグを設定し、起動してみます。
Route53のレコード確認
IPはマスクしていますが、このような形でレコードが登録されます。
CloudWatch Log確認
こちらもIPはマスクしていますが、このような形で表示されます。 EC2を停止した場合は、登録したAレコードが削除されます。
最後に
Serverless Frameworkが、CloudFormationでデプロイされることさえ、使ってみるまで知らなかったです。。。CloudFormationの変更セット適用するとき、差分チェックやら置換の有無やら結構たいへんだったなぁという思い出に浸りました。読んでいただきありがとうございました。