今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があることで苦労した点もあり( 前編参照 )、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます。 前編では Proxy 層の構成として、主に Nginx を使用した Path Based Routing 周りについてのお話でした。後編では App 層で使用した EC2、 Systems Manager パラメータストアあたりについて共有いたします。 App 層の構成 App 層の方針や構築の流れ等をまとめると以下の通りです。 ゴールデンイメージとして OS 設定やサーバアプリケーションをインストールした image(AMI)を作成しておく 上記の AMI を元に、クライアント毎に EC2 インスタンスを作成する インスタンス作成時に必要な Tag の値や環境変数を設定しておく 環境変数はパラメータストアに登録 EC2 インスタンス起動時に、クライアントに応じた Tag や環境変数をもとにサーバアプリケーションのセットアップを行う 自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する それでは、それぞれの詳細について説明していきたいと思います。 AMI 作成 Packer を使用して各インスタンス共通となる AMI を作成します。 provisioners で指定した構築用スクリプトで OS 設定や必要ライブラリ、またメインとなるサーバアプリケーションをインストールします。また、cloud-init を使用して初回起動時に動かすスクリプト類もコピーしておきます。 なお、cloud-init から実行するスクリプトは Git や S3 などから動的に取得する方法もありますが、さほどスクリプトの内容に変更は発生しない点と、内容的に変更ある場合は image 再作成がどちらにしても必要になりそうだったので割り切って image 内に含めることにしています。 作成した packer.json の provisioners 部分を抜粋するとこのような感じになります(説明コメント部分は実際には記載していません) "provisioners" : [ -- type: shell として、構築用スクリプト指定。ビルド時に実行される { "type" : "shell" , "scripts" : [ "scripts/provision.sh" ] }, -- type: file でインスタンス起動時に実行させるスクリプト群をコピー -- これらのスクリプトは cloud-init から実行される(cloud-init の設定は別途インスタンス作成時に行っている) { "type" : "file" , "source" : "./scripts" , "destination" : "/home/hoge" }, -- 上記のスクリプトに対して実行権限付与 { "type" : "shell" , "inline" : [ "chmod +x /home/hoge/scripts/*" ] } ] packer build でビルドした image が AWS に今回の共通で使用する AMI として登録されます EC2 インスタンス作成 作成した AMI を元に、クライアントごとのインスタンスを作成します。なお、インスタンス作成は Terraform や CloudFormation などは使わず、AWS CLI を利用したスクリプトを作成して実行しています。 インスタンス作成スクリプトはこのような流れの処理となります。 引数でクライアント識別 ID やその他サーバアプリケーションセットアップに必要となる環境変数を指定 AWS CLI で EC2 インスタンス作成 引数で指定された環境変数を AWS CLI でパラメータストアに登録 インスタンス作成 以下のように、 aws ec2 run-instances コマンドを使用し、Tag にクライアント識別 ID を指定して作成しています。 ここで指定したクライアント識別 ID を元にパラメータストアから自分用の環境変数を登録/取得したり、Private DNS 用のドメインに使用します。 aws ec2 run-instances \ --image-id ${ AMI_ID } \ --key-name ${ KEY_NAME } --region ${ REGION } \ --subnet-id ${ SUBNET_ID } \ --security-group-ids ${ SECURITY_GROUP } \ --user-data file:// ${ USER_DATA } \ --instance-type ${ INSTANCE_TYPE } \ --tag-specifications "ResourceType=instance,Tags=[{Key=ClientId,Value=${ CLIENT_ID }}]" \ --iam-instance-profile "Arn=${ SERVICE_ROLE }" user-data には初回起動時に実行したいスクリプト(Packer でビルド時にコピーしておいたスクリプト)を指定しているだけとなります。 #!/bin/bash /home/hoge/scripts/bootstrap.sh パラメータストアに環境変数登録 使用する環境変数は、Key は共通ですが値がクライアントによって異なります。そのため、HOGE という Key を使用する場合、 <クライアント識別 ID>.HOGE という形式でパラメータストアに登録しています。 (注. パラメータストアに 階層やタグ付けがサポートされた らしく、このあたりの構成は今後見直す予定です) 登録は aws ssm put-parameter を実行します aws ssm put-parameter \ --name ${ KEY } \ --value ${ VALUE } \ --type ${ PARAMETER_TYPE } \ # String、SecureString など --overwrite これでクライアントごとの EC2 インスタンスが作成、起動されます。次にインスタンス起動時の流れについてです。 EC2 インスタンス起動 起動時は、初回起動と毎回起動でそれぞれ以下のような処理を行います。 初回: パラメータストアから自身に関連する環境変数を取得し、サーバアプリケーションのセットアップ 毎回: 自身の内部 IP を Route53 の Private DNS に登録/更新 内部 IP は固定しておらず起動時に割り振られるため、毎回更新するようにしています。 それではそれぞれの内容について見ていきます。 パラメータストアから環境変数取得 登録時の内容で記載しましたが、環境変数は <クライアント識別 ID>.HOGE という形式で登録しています。そのため、まずは自身のクライアント識別 ID を判定した後に必要な環境変数を aws ssm get-parameters で取得します。 # 自身のインスタンス ID をメタデータから取得 INSTANCE_ID =$( curl -s https://169.254.169.254/latest/meta-data/instance-id ) # クライアント識別 ID をインスタンス作成時に指定した Tag から取得 # (describe-instances の filter に自身のインスタンス ID を指定) CLIENT_ID_TAG =$( aws ec2 describe-instances \ --region=${ REGION } \ --filters "Name=instance-id,Values=${ INSTANCE_ID }" \ | jq -r '.Reservations[].Instances[].Tags[] | select(.Key == "ClientId").Value' ) # 環境変数を取得 # タイプを SecureString にしている変数もあるため、一律 --with-decryption オプションを指定している HOGE =$( aws ssm get-parameters \ --name "${ CLIENT_ID_TAG }.HOGE" \ --with-decryption --region ${ REGION } \ | jq -r ".Parameters[].Value" ) export HOGE =${ HOGE } 内部 IP を Private DNS に登録 最後に、Proxy 層から Private DNS で名前解決できるように自身の IP を Route 53 に登録してやります。 なお、Route53 には事前に対象の Hosted Zone を Private Hosted Zone for Amazon VPC タイプとして登録しておきます。ここでは例として Domain Name を local とします。 EC2 インスタンスから登録される RecordSet は以下の形式とします。 Name: <クライアント識別 ID>.local Type: CNAME Value: EC2 インスタンスの内部 IP これらを行うスクリプト例は以下となります。 # 内部 IP を取得 # (describe-instances の filter に自身のインスタンス ID を指定) PRIVATE_IP =$( aws ec2 describe-instances \ --region=${ REGION } \ --filters "Name=instance-id,Values=${ INSTANCE_ID }" \ | jq -r '.Reservations[].Instances[].PrivateIpAddress' ) # Route53 の登録先 Hosted Zone ID を取得 # SEARCH_KEY は今回の例でいうと 'local.' になります HOSTED_ZONE_ID =$( aws route53 list-hosted-zones \ --region=${ REGION } \ | jq -r ".HostedZones[] | select(.Name == \" ${ SEARCH_KEY } \" ).Id" ) # この後の登録コマンドで指定するための定義ファイル # 毎起動時の登録用(IP が変わるため)に、Action には 'UPSERT' を指定 RECORDSET_FILE = "/tmp/create_recordset.json" cat << EOT > ${ RECORDSET_FILE } { "Changes": [ { "Action": "UPSERT", "ResourceRecordSet": { "Name": "<クライアント識別 ID>.local", "Type": "CNAME", "TTL": 300, "ResourceRecords": [ { "Value": "${ PRIVATE_IP }" } ] } } ] } EOT # 作成した定義ファイルを指定し、Route53 に登録 aws route53 change-resource-record-sets \ --hosted-zone-id ${ HOSTED_ZONE_ID } \ --change-batch file:/// ${ RECORDSET_FILE } 実行するステップはやや多いですが、このような構成をとることで VPC 内ではドメイン指定でのアクセスが可能となるため、IP を意識する必要がなくなるため柔軟な構成になるかと思います。 今回のまとめ いまさらインスタンス立てるとかめんどくさいなぁ、、、とか思いながら色々調べて構築しましたが、EC2 まわりのサービスも増えてるんだなぁ、なんて感じました(特にパラメータストアはとても便利) パラメータストア以外にも Systems Manager には Run Command や Patch Manager など EC2 インスタンスを管理する上でとても便利な仕組みが揃っていますのでこのあたりも導入していきたいと思います。 余談ですが、Systems Manager の存在は re:Invent 2016 で発表された時から名前だけは知ってましたが、今回の対応するまでずっとオンプレ専用のサービスだと勘違いしてて記憶から消えかけていました。。。 最後に 前編を Proxy 層 (Nginx)、後編を App 層(EC2)について書かせていただきましたがいかかだったでしょうか。 そもそもの要件自体がけっこう特殊だったりもするので、なんでこんな構成に?みたいなとこもあるかも知れませんが、どなたかの参考になれば幸いです。もう少し聞いてみたい、というかたは wantedly の「 話を聞いてみたい 」ボタンからどうぞ。 ※前編をあらためて読みたい方はこちらからどうぞ https://developer.medley.jp/entry/2017/08/24/120000_01 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があることで苦労した点もあり( 前編参照 )、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます。 前編では Proxy 層の構成として、主に Nginx を使用した Path Based Routing 周りについてのお話でした。後編では App 層で使用した EC2、 Systems Manager パラメータストアあたりについて共有いたします。 App 層の構成 App 層の方針や構築の流れ等をまとめると以下の通りです。 ゴールデンイメージとして OS 設定やサーバアプリケーションをインストールした image(AMI)を作成しておく 上記の AMI を元に、クライアント毎に EC2 インスタンスを作成する インスタンス作成時に必要な Tag の値や環境変数を設定しておく 環境変数はパラメータストアに登録 EC2 インスタンス起動時に、クライアントに応じた Tag や環境変数をもとにサーバアプリケーションのセットアップを行う 自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する それでは、それぞれの詳細について説明していきたいと思います。 AMI 作成 Packer を使用して各インスタンス共通となる AMI を作成します。 provisioners で指定した構築用スクリプトで OS 設定や必要ライブラリ、またメインとなるサーバアプリケーションをインストールします。また、cloud-init を使用して初回起動時に動かすスクリプト類もコピーしておきます。 なお、cloud-init から実行するスクリプトは Git や S3 などから動的に取得する方法もありますが、さほどスクリプトの内容に変更は発生しない点と、内容的に変更ある場合は image 再作成がどちらにしても必要になりそうだったので割り切って image 内に含めることにしています。 作成した packer.json の provisioners 部分を抜粋するとこのような感じになります(説明コメント部分は実際には記載していません) "provisioners" : [ -- type: shell として、構築用スクリプト指定。ビルド時に実行される { "type" : "shell" , "scripts" : [ "scripts/provision.sh" ] }, -- type: file でインスタンス起動時に実行させるスクリプト群をコピー -- これらのスクリプトは cloud-init から実行される(cloud-init の設定は別途インスタンス作成時に行っている) { "type" : "file" , "source" : "./scripts" , "destination" : "/home/hoge" }, -- 上記のスクリプトに対して実行権限付与 { "type" : "shell" , "inline" : [ "chmod +x /home/hoge/scripts/*" ] } ] packer build でビルドした image が AWS に今回の共通で使用する AMI として登録されます EC2 インスタンス作成 作成した AMI を元に、クライアントごとのインスタンスを作成します。なお、インスタンス作成は Terraform や CloudFormation などは使わず、AWS CLI を利用したスクリプトを作成して実行しています。 インスタンス作成スクリプトはこのような流れの処理となります。 引数でクライアント識別 ID やその他サーバアプリケーションセットアップに必要となる環境変数を指定 AWS CLI で EC2 インスタンス作成 引数で指定された環境変数を AWS CLI でパラメータストアに登録 インスタンス作成 以下のように、 aws ec2 run-instances コマンドを使用し、Tag にクライアント識別 ID を指定して作成しています。 ここで指定したクライアント識別 ID を元にパラメータストアから自分用の環境変数を登録/取得したり、Private DNS 用のドメインに使用します。 aws ec2 run-instances \ --image-id ${ AMI_ID } \ --key-name ${ KEY_NAME } --region ${ REGION } \ --subnet-id ${ SUBNET_ID } \ --security-group-ids ${ SECURITY_GROUP } \ --user-data file:// ${ USER_DATA } \ --instance-type ${ INSTANCE_TYPE } \ --tag-specifications "ResourceType=instance,Tags=[{Key=ClientId,Value=${ CLIENT_ID }}]" \ --iam-instance-profile "Arn=${ SERVICE_ROLE }" user-data には初回起動時に実行したいスクリプト(Packer でビルド時にコピーしておいたスクリプト)を指定しているだけとなります。 #!/bin/bash /home/hoge/scripts/bootstrap.sh パラメータストアに環境変数登録 使用する環境変数は、Key は共通ですが値がクライアントによって異なります。そのため、HOGE という Key を使用する場合、 <クライアント識別 ID>.HOGE という形式でパラメータストアに登録しています。 (注. パラメータストアに 階層やタグ付けがサポートされた らしく、このあたりの構成は今後見直す予定です) 登録は aws ssm put-parameter を実行します aws ssm put-parameter \ --name ${ KEY } \ --value ${ VALUE } \ --type ${ PARAMETER_TYPE } \ # String、SecureString など --overwrite これでクライアントごとの EC2 インスタンスが作成、起動されます。次にインスタンス起動時の流れについてです。 EC2 インスタンス起動 起動時は、初回起動と毎回起動でそれぞれ以下のような処理を行います。 初回: パラメータストアから自身に関連する環境変数を取得し、サーバアプリケーションのセットアップ 毎回: 自身の内部 IP を Route53 の Private DNS に登録/更新 内部 IP は固定しておらず起動時に割り振られるため、毎回更新するようにしています。 それではそれぞれの内容について見ていきます。 パラメータストアから環境変数取得 登録時の内容で記載しましたが、環境変数は <クライアント識別 ID>.HOGE という形式で登録しています。そのため、まずは自身のクライアント識別 ID を判定した後に必要な環境変数を aws ssm get-parameters で取得します。 # 自身のインスタンス ID をメタデータから取得 INSTANCE_ID =$( curl -s https://169.254.169.254/latest/meta-data/instance-id ) # クライアント識別 ID をインスタンス作成時に指定した Tag から取得 # (describe-instances の filter に自身のインスタンス ID を指定) CLIENT_ID_TAG =$( aws ec2 describe-instances \ --region=${ REGION } \ --filters "Name=instance-id,Values=${ INSTANCE_ID }" \ | jq -r '.Reservations[].Instances[].Tags[] | select(.Key == "ClientId").Value' ) # 環境変数を取得 # タイプを SecureString にしている変数もあるため、一律 --with-decryption オプションを指定している HOGE =$( aws ssm get-parameters \ --name "${ CLIENT_ID_TAG }.HOGE" \ --with-decryption --region ${ REGION } \ | jq -r ".Parameters[].Value" ) export HOGE =${ HOGE } 内部 IP を Private DNS に登録 最後に、Proxy 層から Private DNS で名前解決できるように自身の IP を Route 53 に登録してやります。 なお、Route53 には事前に対象の Hosted Zone を Private Hosted Zone for Amazon VPC タイプとして登録しておきます。ここでは例として Domain Name を local とします。 EC2 インスタンスから登録される RecordSet は以下の形式とします。 Name: <クライアント識別 ID>.local Type: CNAME Value: EC2 インスタンスの内部 IP これらを行うスクリプト例は以下となります。 # 内部 IP を取得 # (describe-instances の filter に自身のインスタンス ID を指定) PRIVATE_IP =$( aws ec2 describe-instances \ --region=${ REGION } \ --filters "Name=instance-id,Values=${ INSTANCE_ID }" \ | jq -r '.Reservations[].Instances[].PrivateIpAddress' ) # Route53 の登録先 Hosted Zone ID を取得 # SEARCH_KEY は今回の例でいうと 'local.' になります HOSTED_ZONE_ID =$( aws route53 list-hosted-zones \ --region=${ REGION } \ | jq -r ".HostedZones[] | select(.Name == \" ${ SEARCH_KEY } \" ).Id" ) # この後の登録コマンドで指定するための定義ファイル # 毎起動時の登録用(IP が変わるため)に、Action には 'UPSERT' を指定 RECORDSET_FILE = "/tmp/create_recordset.json" cat << EOT > ${ RECORDSET_FILE } { "Changes": [ { "Action": "UPSERT", "ResourceRecordSet": { "Name": "<クライアント識別 ID>.local", "Type": "CNAME", "TTL": 300, "ResourceRecords": [ { "Value": "${ PRIVATE_IP }" } ] } } ] } EOT # 作成した定義ファイルを指定し、Route53 に登録 aws route53 change-resource-record-sets \ --hosted-zone-id ${ HOSTED_ZONE_ID } \ --change-batch file:/// ${ RECORDSET_FILE } 実行するステップはやや多いですが、このような構成をとることで VPC 内ではドメイン指定でのアクセスが可能となるため、IP を意識する必要がなくなるため柔軟な構成になるかと思います。 今回のまとめ いまさらインスタンス立てるとかめんどくさいなぁ、、、とか思いながら色々調べて構築しましたが、EC2 まわりのサービスも増えてるんだなぁ、なんて感じました(特にパラメータストアはとても便利) パラメータストア以外にも Systems Manager には Run Command や Patch Manager など EC2 インスタンスを管理する上でとても便利な仕組みが揃っていますのでこのあたりも導入していきたいと思います。 余談ですが、Systems Manager の存在は re:Invent 2016 で発表された時から名前だけは知ってましたが、今回の対応するまでずっとオンプレ専用のサービスだと勘違いしてて記憶から消えかけていました。。。 最後に 前編を Proxy 層 (Nginx)、後編を App 層(EC2)について書かせていただきましたがいかかだったでしょうか。 そもそもの要件自体がけっこう特殊だったりもするので、なんでこんな構成に?みたいなとこもあるかも知れませんが、どなたかの参考になれば幸いです。もう少し聞いてみたい、というかたは wantedly の「 話を聞いてみたい 」ボタンからどうぞ。 ※前編をあらためて読みたい方はこちらからどうぞ https://developer.medley.jp/entry/2017/08/24/120000_01 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があることで苦労した点もあり( 前編参照 )、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます。 前編では Proxy 層の構成として、主に Nginx を使用した Path Based Routing 周りについてのお話でした。後編では App 層で使用した EC2、 Systems Manager パラメータストアあたりについて共有いたします。 App 層の構成 App 層の方針や構築の流れ等をまとめると以下の通りです。 ゴールデンイメージとして OS 設定やサーバアプリケーションをインストールした image(AMI)を作成しておく 上記の AMI を元に、クライアント毎に EC2 インスタンスを作成する インスタンス作成時に必要な Tag の値や環境変数を設定しておく 環境変数はパラメータストアに登録 EC2 インスタンス起動時に、クライアントに応じた Tag や環境変数をもとにサーバアプリケーションのセットアップを行う 自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する それでは、それぞれの詳細について説明していきたいと思います。 AMI 作成 Packer を使用して各インスタンス共通となる AMI を作成します。 provisioners で指定した構築用スクリプトで OS 設定や必要ライブラリ、またメインとなるサーバアプリケーションをインストールします。また、cloud-init を使用して初回起動時に動かすスクリプト類もコピーしておきます。 なお、cloud-init から実行するスクリプトは Git や S3 などから動的に取得する方法もありますが、さほどスクリプトの内容に変更は発生しない点と、内容的に変更ある場合は image 再作成がどちらにしても必要になりそうだったので割り切って image 内に含めることにしています。 作成した packer.json の provisioners 部分を抜粋するとこのような感じになります(説明コメント部分は実際には記載していません) "provisioners" : [ -- type: shell として、構築用スクリプト指定。ビルド時に実行される { "type" : "shell" , "scripts" : [ "scripts/provision.sh" ] }, -- type: file でインスタンス起動時に実行させるスクリプト群をコピー -- これらのスクリプトは cloud-init から実行される(cloud-init の設定は別途インスタンス作成時に行っている) { "type" : "file" , "source" : "./scripts" , "destination" : "/home/hoge" }, -- 上記のスクリプトに対して実行権限付与 { "type" : "shell" , "inline" : [ "chmod +x /home/hoge/scripts/*" ] } ] packer build でビルドした image が AWS に今回の共通で使用する AMI として登録されます EC2 インスタンス作成 作成した AMI を元に、クライアントごとのインスタンスを作成します。なお、インスタンス作成は Terraform や CloudFormation などは使わず、AWS CLI を利用したスクリプトを作成して実行しています。 インスタンス作成スクリプトはこのような流れの処理となります。 引数でクライアント識別 ID やその他サーバアプリケーションセットアップに必要となる環境変数を指定 AWS CLI で EC2 インスタンス作成 引数で指定された環境変数を AWS CLI でパラメータストアに登録 インスタンス作成 以下のように、 aws ec2 run-instances コマンドを使用し、Tag にクライアント識別 ID を指定して作成しています。 ここで指定したクライアント識別 ID を元にパラメータストアから自分用の環境変数を登録/取得したり、Private DNS 用のドメインに使用します。 aws ec2 run-instances \ --image-id ${ AMI_ID } \ --key-name ${ KEY_NAME } --region ${ REGION } \ --subnet-id ${ SUBNET_ID } \ --security-group-ids ${ SECURITY_GROUP } \ --user-data file:// ${ USER_DATA } \ --instance-type ${ INSTANCE_TYPE } \ --tag-specifications "ResourceType=instance,Tags=[{Key=ClientId,Value=${ CLIENT_ID }}]" \ --iam-instance-profile "Arn=${ SERVICE_ROLE }" user-data には初回起動時に実行したいスクリプト(Packer でビルド時にコピーしておいたスクリプト)を指定しているだけとなります。 #!/bin/bash /home/hoge/scripts/bootstrap.sh パラメータストアに環境変数登録 使用する環境変数は、Key は共通ですが値がクライアントによって異なります。そのため、HOGE という Key を使用する場合、 <クライアント識別 ID>.HOGE という形式でパラメータストアに登録しています。 (注. パラメータストアに 階層やタグ付けがサポートされた らしく、このあたりの構成は今後見直す予定です) 登録は aws ssm put-parameter を実行します aws ssm put-parameter \ --name ${ KEY } \ --value ${ VALUE } \ --type ${ PARAMETER_TYPE } \ # String、SecureString など --overwrite これでクライアントごとの EC2 インスタンスが作成、起動されます。次にインスタンス起動時の流れについてです。 EC2 インスタンス起動 起動時は、初回起動と毎回起動でそれぞれ以下のような処理を行います。 初回: パラメータストアから自身に関連する環境変数を取得し、サーバアプリケーションのセットアップ 毎回: 自身の内部 IP を Route53 の Private DNS に登録/更新 内部 IP は固定しておらず起動時に割り振られるため、毎回更新するようにしています。 それではそれぞれの内容について見ていきます。 パラメータストアから環境変数取得 登録時の内容で記載しましたが、環境変数は <クライアント識別 ID>.HOGE という形式で登録しています。そのため、まずは自身のクライアント識別 ID を判定した後に必要な環境変数を aws ssm get-parameters で取得します。 # 自身のインスタンス ID をメタデータから取得 INSTANCE_ID =$( curl -s https://169.254.169.254/latest/meta-data/instance-id ) # クライアント識別 ID をインスタンス作成時に指定した Tag から取得 # (describe-instances の filter に自身のインスタンス ID を指定) CLIENT_ID_TAG =$( aws ec2 describe-instances \ --region=${ REGION } \ --filters "Name=instance-id,Values=${ INSTANCE_ID }" \ | jq -r '.Reservations[].Instances[].Tags[] | select(.Key == "ClientId").Value' ) # 環境変数を取得 # タイプを SecureString にしている変数もあるため、一律 --with-decryption オプションを指定している HOGE =$( aws ssm get-parameters \ --name "${ CLIENT_ID_TAG }.HOGE" \ --with-decryption --region ${ REGION } \ | jq -r ".Parameters[].Value" ) export HOGE =${ HOGE } 内部 IP を Private DNS に登録 最後に、Proxy 層から Private DNS で名前解決できるように自身の IP を Route 53 に登録してやります。 なお、Route53 には事前に対象の Hosted Zone を Private Hosted Zone for Amazon VPC タイプとして登録しておきます。ここでは例として Domain Name を local とします。 EC2 インスタンスから登録される RecordSet は以下の形式とします。 Name: <クライアント識別 ID>.local Type: CNAME Value: EC2 インスタンスの内部 IP これらを行うスクリプト例は以下となります。 # 内部 IP を取得 # (describe-instances の filter に自身のインスタンス ID を指定) PRIVATE_IP =$( aws ec2 describe-instances \ --region=${ REGION } \ --filters "Name=instance-id,Values=${ INSTANCE_ID }" \ | jq -r '.Reservations[].Instances[].PrivateIpAddress' ) # Route53 の登録先 Hosted Zone ID を取得 # SEARCH_KEY は今回の例でいうと 'local.' になります HOSTED_ZONE_ID =$( aws route53 list-hosted-zones \ --region=${ REGION } \ | jq -r ".HostedZones[] | select(.Name == \" ${ SEARCH_KEY } \" ).Id" ) # この後の登録コマンドで指定するための定義ファイル # 毎起動時の登録用(IP が変わるため)に、Action には 'UPSERT' を指定 RECORDSET_FILE = "/tmp/create_recordset.json" cat << EOT > ${ RECORDSET_FILE } { "Changes": [ { "Action": "UPSERT", "ResourceRecordSet": { "Name": "<クライアント識別 ID>.local", "Type": "CNAME", "TTL": 300, "ResourceRecords": [ { "Value": "${ PRIVATE_IP }" } ] } } ] } EOT # 作成した定義ファイルを指定し、Route53 に登録 aws route53 change-resource-record-sets \ --hosted-zone-id ${ HOSTED_ZONE_ID } \ --change-batch file:/// ${ RECORDSET_FILE } 実行するステップはやや多いですが、このような構成をとることで VPC 内ではドメイン指定でのアクセスが可能となるため、IP を意識する必要がなくなるため柔軟な構成になるかと思います。 今回のまとめ いまさらインスタンス立てるとかめんどくさいなぁ、、、とか思いながら色々調べて構築しましたが、EC2 まわりのサービスも増えてるんだなぁ、なんて感じました(特にパラメータストアはとても便利) パラメータストア以外にも Systems Manager には Run Command や Patch Manager など EC2 インスタンスを管理する上でとても便利な仕組みが揃っていますのでこのあたりも導入していきたいと思います。 余談ですが、Systems Manager の存在は re:Invent 2016 で発表された時から名前だけは知ってましたが、今回の対応するまでずっとオンプレ専用のサービスだと勘違いしてて記憶から消えかけていました。。。 最後に 前編を Proxy 層 (Nginx)、後編を App 層(EC2)について書かせていただきましたがいかかだったでしょうか。 そもそもの要件自体がけっこう特殊だったりもするので、なんでこんな構成に?みたいなとこもあるかも知れませんが、どなたかの参考になれば幸いです。もう少し聞いてみたい、というかたは wantedly の「 話を聞いてみたい 」ボタンからどうぞ。 ※前編をあらためて読みたい方はこちらからどうぞ https://developer.medley.jp/entry/2017/08/24/120000_01 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal 今回の内容について メドレー開発本部の田中です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ... developer.medley.jp 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita balancer_by_lua_xxxxx いつの間にやら1 lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。 以下ドキュメントより抜粋。 http { upstream backend {... qiita.com お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 https://developer.medley.jp/entry/2017/08/24/120000_02 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 https://qiita.com/toritori0318/items/a9305d528b52936c0573 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 https://www.medley.jp/recruit/creative.html
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal 今回の内容について メドレー開発本部の田中です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ... developer.medley.jp 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita balancer_by_lua_xxxxxいつの間にやら[^1] lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。以下ド… qiita.com お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal 今回の内容について メドレー開発本部の田中です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ... developer.medley.jp 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita balancer_by_lua_xxxxxいつの間にやら[^1] lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。以下ド… qiita.com お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal 今回の内容について メドレー開発本部の田中です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ... developer.medley.jp 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita balancer_by_lua_xxxxx いつの間にやら1 lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。 以下ドキュメントより抜粋。 http { upstream backend {... qiita.com お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal 今回の内容について メドレー開発本部の田中です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ... developer.medley.jp 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita balancer_by_lua_xxxxx いつの間にやら1 lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。 以下ドキュメントより抜粋。 http { upstream backend {... qiita.com お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal 今回の内容について メドレー開発本部の田中です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ... developer.medley.jp 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita balancer_by_lua_xxxxx いつの間にやら1 lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。 以下ドキュメントより抜粋。 http { upstream backend {... qiita.com お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
今回の内容について メドレー開発本部の 田中 です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、後述する制約から苦労した点もあり、制約を乗り越えるための工夫も含めてお話できる限り共有させていただきます(先にお伝えしておくと、特殊な事情がなければ今回のようなケースでは ALB で対応する ECS サービスに Path Based Routing してやるのが良いと思います)。 技術要素として、Nginx(OpenResty)/ Route53 Private DNS / EC2 / Systems Manager パラメータストア あたりに触れたいと思います。(Beanstalk は Multicontainer Docker を使用し、それも慣れるまでちょっとクセあったなぁと思ったのですが分量が多くなりそうなのでまた別の機会に共有させて頂きます) まず前編として Proxy 層、主に Nginx を使用した Dynamic Path Based Routing についてお話して、 後編は App 層 について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 設計/構築する上での前提と方針 対象となる案件を進める上での要件・制限内容は諸事情あり、ざっとまとめるとこのような感じです。 環境は AWS を使用する サーバアプリケーション、クライアントアプリケーションはユーザ毎で、サーバアプリケーションは共用できない(ユーザが増える度にクライアント/サーバのセットが増えるイメージ) ただし、クライアントからの接続先となる Endpoint は同じだが、Host Based Routing は訳あって利用できない クライアント認証を使用する 上記から、以下の設計方針で進める事にしました。 Proxy 層でクライアント認証を行い、Path Based Routing で対象となるサーバにリクエストを proxy する。Path 部分にクライアント別の識別 ID を含め、その値を元に Private DNS で名前解決する 例) https://example.com/a-client/api => https://a-client.local/api App 層は個別 EC2 インスタンスとする 設計する上で悩んだ点 主に 2 点ありますが、まずは Proxy 層です。出来るだけ AWS のマネージド・サービスで済ませたかったので、クライアント認証と Path Based Routing が可能でやりたい事に合うかどうか調べましたが以下の理由で断念し、普通(?)に ELB + Nginx を利用することにしました。 ALB: クライアント認証に非対応。また SSL 終端となるので Nginx 側でクライアント認証が出来ない API GW: クライアント認証は対応しており Routing 部分もがんばればいけるかも?、と思ったが Proxy 先が動的に増えたリするので管理ふくめ難しそうであった 次に App 層の構成をどうするかでした。集積度を高めるためにコンテナ利用も検討したのですが、使用するアプリケーションの必要スペックや要件などからいまいちフィットせず、個別の EC2 インスタンスにすることにしました(今でももっと良い方法がないか悩んでたりします) 全体構成 出来上がった全体構成のイメージは以下となります。なお台数は実環境と異なり、今回の内容と関係ない部分などは省略しています。 次に、今回の本題となる Proxy 層の構成について触れたいと思います。 Proxy 層の構成 Proxy 層の方針等はまとめると以下の通りで、proxy 先の動的判定と名前解決する箇所がキモとなります。 App 層のインスタンスは、起動時に自身の内部 IP と Tag に設定したクライアント識別 ID を元に Route53 の PrivateDNS に登録する クライアント識別 ID が a-client の場合、a-client.local のように登録 Proxy 層の Nginx はクライアント認証を行い、リクエストパスから取り出したクライアント識別 ID を元に転送先 Endpoint を生成し、backend に proxy する App 層のインスタンスは動的に増えるため、リクエスト時に名前解決したい(インスタンスが増える度に自動で Nginx の conf を編集することも検討したが追加数が読めず、conf がふくれあがるのもなぁ、、、という思いがあり止めました) Nginx は backend が増えても起動しっぱなしで動的に名前解決して動作させたかったため、lua-nginx-module を導入し balancer_by_lua ディレクティブと lua-resty-dns モジュールを使用することとし、構築の手間の関係から OpenResty を導入することにしました。 lua-nginx-module を使用した conf ファイル conf ファイル全体としては以下となります(関係ない箇所は省いています)。ポイントと記載した部分についての説明は後述します。 http { upstream app { # ポイント 1. # Private DNS で設定した IP(CNAME に設定)を元に動的 Routing balancer_by_lua_block { local balancer = require "ngx.balancer" local host = ngx . ctx . upstream_server . cname local port = '8888' local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } } server { listen 443 ssl; set $ proxy_upstream_host '' ; set $ proxy_upstream_domain '.local' ; location ^~ /api/ { rewrite_by_lua_block { -- path からクライアント識別 ID を取得し、Private DNS に設定したドメインを生成 -- https://example.com/<id>/api という形式のリクエストから、<id>.local というドメインを生成して -- ngx.var.proxy_upstream_host 変数に格納 local ngx_re = require "ngx.re" local res , err = ngx_re . split ( ngx . var . request_uri , "/" , nil , { pos = 0 }) local id = res[3] ngx.var. proxy_upstream_host = id..ngx.var.proxy_upstream_domain; -- resolver 設定 local resolver = require "resty.dns.resolver" local r, err = resolver:new{ nameservers = {{ "x.x.x.x" , 53}}, -- 使用する nameserver } if not r then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end -- ポイント 2. -- 生成したドメイン名(<id>.local)を元に名前解決し、取得した結果を ngx.ctx にセット -- (balancer_by_lua_block で使用する) local answers, err = r:query(ngx.var.proxy_upstream_host, { qtype = r.TYPE_CNAME }) if not answers then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end if answers.errcode then ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR) end ngx.ctx.upstream_server = answers[1] } proxy_set_header Host $ host ; proxy_set_header X-Real-IP $ remote_addr ; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for ; proxy_set_header X-Forwarded-Proto $ scheme ; -- https://<id>.local/api に proxy rewrite ^/api/(.+)$ /api/ break; proxy_pass https://app; } } } ポイント 1. 動的 Routing balancer.set_current_peer にて proxy 先を動的に設定します。 host 部分にはドメインを直接指定することができないため、ポイント 2. で ngx.ctx にセットした DNS の値から IP(Route53 に CNAME レコードとして設定している)を指定しています。 balancer_by_lua_block { local balancer = require "ngx.balancer" -- ngx.ctx にセットしていた、Private DNS から取得した内部 IP をセット local host = ngx . ctx . upstream_server . cname local port = '8888' -- proxy 先セット。host にドメインは直接指定できない local ok , err = balancer . set_current_peer ( host , port ) if not ok then return ngx . exit ( 500 ) end } 詳細については OpenResty の ドキュメント を参照してください ポイント 2. 動的名前解決 r:query にて、生成したドメイン名( <id>.local )を問い合わせます。 r 部分は resolver:new で nameserver を指定した resolver となります。 なお、nameserver に指定する IP は今回は Route53 の Private DNS を指定するため、外部 nameserver ではなくローカルの nameserver(10.0.0.2 など)を指定することになります。 問い合わせ結果の answers 部分は Lua table 形式の配列となります。今回の例でいうと対象は 1 件となるので、その値を balancer_by_lua_block で使用するために ngx.ctx にセットしています。 local answers , err = r : query ( ngx . var . proxy_upstream_host , { qtype = r . TYPE_CNAME }) if not answers then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end if answers . errcode then ngx . exit ( ngx . HTTP_INTERNAL_SERVER_ERROR ) end ngx . ctx . upstream_server = answers [ 1 ] 詳細については OpenResty の ドキュメント を参照してください 今回のまとめ upstream 先を動的に判定して proxy するという要件はそうそう無いかもしれませんし、途中までは複雑な構成になりそうだなぁとドキドキしてしましたが、結果としてはそれなりにシンプルになったかなと思います。今更ながら Nginx(と lua module)は柔軟で良く出来てるなぁという感想でした。 後編は App 層について、EC2 と Systems Manager パラメータストアあたりについて共有させていただければと思います。 クライアント認証と Path Based Routing が必要なサーバを AWS で構築(後編:App 層) | MEDLEY Developer Portal 今回の内容について メドレー開発本部の田中です。 先日、Proxy 層を Elastic Beanstalk 上の Nginx で、App 層を EC2 インスタンスで構築する機会がありました。ここだけ見るととても普通に見えますが、制約があ... developer.medley.jp 参考リンク 構築にあたり、下記記事を参考にさせていただきました。ありがとうございます。 Nginx balancer_by_luaの話とupstream名前解決の話 - Qiita balancer_by_lua_xxxxx いつの間にやら1 lua-nginx-module に balancer_by_lua_xxx という新しいディレクティブが増えていました。 以下ドキュメントより抜粋。 http { upstream backend {... qiita.com お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 メンバーのストーリー | 株式会社メドレー メンバーのストーリー 家族や友人が病気になった時に救いの手を差しのべる医療の力。... www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
開発本部の平山です。先日、社内勉強会「TechLunch」にて社外に公開できない内容の発表をしてしまいましたので、その代わりとして、厚生労働省が提供する「NDB オープンデータ」をオープン化した話について、ブログを書こうと思います。 NDB オープンデータとは? www.mhlw.go.jp 作成の背景 ◆ レセプト情報・特定健診等情報データベース(NDB)は、悉皆性が高いレセプト情報、および検査値などの詳細な情報を有する特定健診等情報が含まれており、国民の医療動向を評価するうえで有用なデータだと考えられている。 ◆ 2011 年度より、医療費適正化計画策定に資する目的以外での NDB データの利用が認められたが、NDB データの機微性の高さに鑑み、利用者に対しては高いレベルのセキュリティ要件を課したうえで、データ提供が行われてきた。 ◆ 一方で、多くの研究者が必ずしも詳細な個票データを必要とするわけではないため、多くの人々が使用できるような、あらかじめ定式化された集計データを NDB データをもとに整備することが重要ではないか、という議論が有識者会議等でなされてきた。 ◆ NDB の民間提供に関する議論でも、「レセプト情報等の提供に関するワーキンググループ」からの報告では、汎用性が高く様々なニーズに一定程度応えうる基礎的な集計表を作成し、公表していくことがむしろ適当である、という指摘がみられた。 作成の目的 ◆ 多くの人々が NDB データに基づいた保健医療に関する知見に接することが出来るよう、NDB データを用いて基礎的な集計表を作成したうえで、公表する。 ◆ NDB データに基づき、医療の提供実態や特定健診等の結果をわかりやすく示す。 要は皆さんが、病院に行った時にもらう明細書に記載されている初診〇〇点、外来診療料〇〇点のようなデータが個人情報が匿名化された状態で収集しその統計データを一般に公開する、といったところでしょうか。 このようなデータがオープンになっていることはとても意義のあることだと思いますし、公開にまでこぎつけた関係者の苦労が想像されます。しかし、このような画期的なデータ提供ではありますが、Excel ファイルでの提供となっており、かつ加工がしづらいデータ構造になっているため、データを細かくみてみようとすると非常に手間がかかるという問題があります。 NDB オープンデータのオープン化 そこで NDB オープンデータとして公開されている Excel ファイルを加工し、DB に格納し BI ツール( Redash )から参照させるようにしてみました。 1. データ加工 & DB 取り込み 公開サイト にある医科診療行為に関する Excel ファイルを取得し、ログテーブルとしてよくあるフォーマットに変換し DB に取り込む。 変換前 変換後 *************************** 1. row *************************** id: 1 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: all revision: 2014 prefecture: sex: age: score: 251700771 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 2. row *************************** id: 2 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 0~4 歳 score: 13158090 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 *************************** 3. row *************************** id: 3 practice_category_code: A000 practice_category_name: 初診料 practice_code: 111000110 practice_name: 初診 practice_type: 外来 target: sex_age revision: 2014 prefecture: sex: 男性 age: 5~9 歳 score: 12444947 created_at: 0000-00-00 00:00:00 updated_at: 0000-00-00 00:00:00 2. データの参照 変換したデータを取り込んだ DB を Redash から参照。分析したいデータを取得するためのクエリを書いてダッシュボード化。 NDB オープンデータの活用例 以下に簡単なデータ活用のサンプルを載せました。医薬診療行為だけでなく特定健診や薬剤のデータを使うともう少し面白い気付きがあるかもしれません。 いずれにせよ、このように加工可能な形でのデータ提供こそがオープンデータ提供の価値だと思うので、このような仕組みが加速すれば良いなと思います。 0-4 歳 男性 診療行為点数 90 歳以上 男性 診療行為点数 140023350 胃瘻より流動食点滴注入 都道府県別 150086210 角膜移植術 年齡別 まとめ 以上、NDB オープンデータをオープン化してみた話について書いてみました。 このように.go.jp から提供されるデータは一般的に Excel や PDF でのファイル提供が基本で、インターネットサービスのように API のような形で提供されることはありません。せっかく貴重なデータが提供されているにも関わらず、それが IT システムと連動しづらいことで、活用されない状況になっているのはとても残念なことに思います。 Code for America の事例ではないですが、もっとインターネット系の人材がこのような取り組みに入り込んでいくようになれば、より合理的でスマートな仕組みが加速し、業界全体の IT 化も加速するのではないでしょうか。 お知らせ メドレーでは、医師たちがつくるオンライン医療事典「 MEDLEY 」、オンライン診療アプリ「 CLINICS 」、医療介護の求人サイト「 ジョブメドレー 」、口コミで探せる介護施設の検索サイト「 介護のほんね 」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。 メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。 www.medley.jp
ビールが美味しい季節ですね! 最近飲みすぎて嫁に叱られて、飲み会自粛中のデザイナー・ マエダ です。 メドレーでは TechLunch という社内勉強会を実施しているのですが、デザインについて私も発表する機会をいただきましたので、その内容を紹介させていただきます。テーマは「DLS の導入について」です。発表資料は記事の最後をご覧ください。 DLS(デザイン言語システム)とは DLS とは DesignLanguageSystem の略で、すごい単純にいえばデザインガイドラインみたいに UI に一貫性をもたせるため、配色やレイアウト、タイポグラフィやマージンなどのルールを策定するもの です。 私が主に担当しているオンライン診療アプリ「 CLINICS 」は、iOS、Android、Web と 3 つのプラットフォームで運用しているのですが、入社した当初はプラットフォーム毎に違った UI やルールで開発しており、サービスとして一貫性のあるサービス体験を提供できるとは言えない状況でした。また新たに機能を追加する際、それぞれ違なるデザインをしなければならず、デザイン作業においても負荷がかかっていました。 DLS が必要な理由 CLINICS ではプラットフォームごとに異なる UI を提供していたため、一貫したサービス品質をユーザーに提供できていないこと、開発者ごとに UI に対して認識にズレが生じていたことが課題でした。それに伴い開発速度も決して速いとは言えず、どうにか一定の品質を担保しつつ開発スピードも改善できないかと悩んでいたところ、Airbnb の開発者ブログで Airbnb Design System という記事を見かけました。 これまでのデザインガイドラインは単にカラーやマージンの定義を取り決めるだけだったのですが、 Airbnb ではデザイン言語として定義し、他の言語と同じようにチームと共有し、エンジニア・デザイナー同士で理解できる設計を作り上げている といった内容でした。 ※参考 medium.com 私がデザイナーとしてサービスデザインに携わる重要な役割のひとつとして、単にデザインをするだけでなく デザインを通して UI 設計の制約をつくり、継続的に運用しやすいプロダクトに仕上げること があると思っています。 DLS を起点として、各プラットフォームの開発者が共通の認識でシステム開発が行えれば、これまで以上にスピーディが開発が行え、一貫性のある体験をユーザーに提供できるプロダクトにできるはずだと考え、CLINICS 独自の DLS を開発することにしました。 CLINICS の DLS の一部 DLS を導入してどうなったか CLINICS では、Android、iOS、web で担当するエンジニアが異なるのですが、DLS を設計しシステムの詳細を共有することで、UI に対しての共通認識が生まれ、一貫した品質を担保できるようになりました。さらにこれまでエンジニアだけでデザインを考えて悩んでいた時間を、コンポーネント設計で組み立てたデザインを DLS で定義したことで、UI に悩むことなく機能ロジックに重点を置いた開発に専念できるようになったのではないかと思います。 デザイン言語でサービス設計の基礎を築けたことで、開発だけに追われていた状況から、より良いサービスを作るためにはどんな UX をユーザーに提供すべきかという声がエンジニアからも頻繁に声が上がりはじめたことも良かった点です。 エンジニア陣が UI について熱い議論を交わしている様子 TechLunch では、こうした内容について実際に作ったコンポーネント設計なども見せながらお話しました。発表資料はこちら。 speakerdeck.com まとめ DLS 導入以前は、エンジニアが開発に追われていたということもあり、プラットフォーム毎に議論ができていなかったエンジニアがお互いの担当プラットフォームを意識したコミュニケーションがとれるようになったことは予想外に良かった点です(別に仲が悪かったとかそういうことではなく w)。 さらに DLS で UI の基盤をつくったことで、デザイナーが手を動かさずともコンポーネントの組み合わせを話し合うだけで、エンジニア完結で一貫した品質で機能実装できるようになりました。これにより私も次の施策やプロジェクトに専念できるようになり、効率的に仕事ができるようになりました。 まだデザインルールに一貫性がないプロジェクトを担当しているデザイナーやエンジニアは、ぜひ DLS の導入を検討してみてはいかがでしょうか。 単にデザイン品質だけでなく、チームコミュニケーションも改善されるとおもいます。 より詳しく話を聞きたいかたは、気軽に「 話を聞きに行きたい 」をクリックしてみてください。ビールを飲みながらデザイン談義をしましょう!(嫁に怒られない程度に w) メドレー開発本部について、もっと詳しく知りたい方はこちらからどうぞ。 www.medley.jp www.wantedly.com