- 投稿日:2020-08-30T21:35:57+09:00
Amplify ConsoleのカスタムドメインをValue-DomainからRoute53へ移管する
前回「AWSにフルサーバーレスなWebサイト運用環境を作る(Gatsby + Amplify + CodeCommit)」で、Amplify Consoleを使ってフルサーバーレスで爆速Webサイトを作りました。
その時はAmplifyのカスタムドメインの管理を外部レジストラ(実はValue-Domain)にCNAME設定したのですが、今回はそのAmplifyで利用中のドメイン管理をValue-DomainからAWS Route53へ移管します。
といってもほとんど公式ガイドの手順の通りなのですが、多少Amplify特有の部分もあるので記載します。1〜2週間かかる可能性があると公式には記載がありましたが、やってみると半日程度でスムーズに移管できました。
Value-Domain側の移管手順を確認する
まずはValue-Domainのドメイン移管手順をざっと確認します。
参考 https://www.value-domain.com/userguide/manual/transferother/
Route53へホストゾーンを設定する
ここからはAWS のRoute53に移管方法が書いてあるのでそれを一読してから進めます。
参考サイト : Route 53 を使用中のドメインの DNS サービスにする
現在の DNS サービスプロバイダから現在の DNS 設定を取得する (オプション、ただし推奨)
念のため(ミスった時用)に、Value-Domain上のドメイン設定をメモしておきます。以下は例です。
cname (対象ドメイン名). master.(サブドメイン).amplifyapp.com. cname www.(対象ドメイン名). master.(サブドメイン).amplifyapp.com. cname stg.(対象ドメイン名). stg.(サブドメイン).amplifyapp.com. cname _(検証文字列).(対象ドメイン名). _(acm検証用サブドメイン).acm-validations.aws.
Route53にホストゾーンを作成する
AWS 管理コンソールへサインインしてRoute53を開き、ホストゾーンを作成します。
domain name: (対象ドメイン名) option: (設定不要) type: public host zone
レコードを作成する
Amplifyのカスタムドメイン用レコードの作成はAmplify Consoleで行います。Route53からではAmplify Console用のAlias Aレコード設定が出来ないので注意が必要です。しかもすでに外部レジストラ用にAmplify にカスタムドメイン設定が済んでいると、それをRoute53へ反映させる方法がわからずちょっと悩みました。
正解は、「ステップ2でホストゾーンを作った後、Amplify Consoleでドメイン設定を更新する(例:いったん無効化→更新→再度有効化→更新)と、Route53のホストゾーンにレコードが追加されました。
Amplifyコンソール > アプリ名 > アプリの設定 > ドメイン管理
サブドメインの管理を選び、(対象ドメイン名)をいったんdisableしてからenableする。
Amplify のDNS設定確認
実際にRoute53のホストゾーンにAmplifyのレコードが登録されたことを確認します。
レコード名 タイプ ルーティングポリシー 差別化要因 値/トラフィックのルーティング先 (対象ドメイン名) A シンプル - (サブドメイン).cloudfront.net. (対象ドメイン名) NS シンプル - ns-NNN.awsdns-00.net. ns-NNN.awsdns-13.com. ns-NNN.awsdns-39.co.uk. ns-NNN.awsdns-31.org. (対象ドメイン名) SOA シンプル - ns-NNN.awsdns-00.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400 stg.(対象ドメイン名) CNAME シンプル - (サブドメイン).cloudfront.net www.(対象ドメイン名) CNAME シンプル - (サブドメイン).cloudfront.net
NameServerをValue-DomainからRoute53へ切り替える
移管前にNameServerをRoute53に切り替えます。
TL の設定を下げる
Value Domain側のDNSレコード編集でTTLを十分短く設定します。(今回はすでに300secに設定してあるので、変更しませんでした)
古い TTL の有効期限切れを待つ
今回はすでに300secに設定してあるので、待ちませんでしたが、TTLを長く設定していた場合、時間がかかります。
現在の DNS サービスプロバイダで NS レコードを更新して Route 53 ネームサーバーを使用する
Route53のHosted ZoneでNSレコードの値を確認する。以下は例。
ns-nnn.awsdns-00.net. ns-nnn.awsdns-13.com. ns-nnn.awsdns-39.co.uk. ns-nnn.awsdns-31.org.Value-domainのネームサーバー設定をRoute53のNSレコードの値に変更します。
Value-domain > コントロールパネル >(ドメイン名) > DNS > ネームサーバーの設定
ネームサーバーの値をRoute53のものに変更して更新します。
ドメインのトラフィックの監視
変更後、ちゃんとドメインへアクセス出来ていることを確認する。
NS レコードの TTL を高い値に戻す
必要に応じて、NSレコードのTTL設定を元に戻します。
ドメインをAmazon Route53へ移管する
ここからが本当の移管手続きです。移管にはValue-Domainが発行する認証鍵が必要です。そのためにValue-Domainのwhois代理公開機能を無効化する必要があります
Value-domain のwhois の代理公開を無効にします。
whois で管理者情報からプライバシーを漏洩させないためにドメインのwhois情報をValue-Domainに代理情報で公開してもらっている場合があります。その場合、認証キーを取得するために無効化が必要です。
Value-domain > コントロールパネル >(ドメイン名) > ドメイン >ドメインの設定・操作>ドメインの登録情報変更[eNom]
「 名義(WHOIS)の代理公開」を無効化します。
Valuedomain の認証鍵をメモする
参考: http://blog.10rane.com/2014/09/16/transferred-a-domain-to-route53/
Value-domain > コントロールパネル >(ドメイン名) > ドメイン >ドメインの設定・操作>ドメインの登録情報変更[eNom]
画面の下にある認証鍵情報をメモします。
認証鍵(Authorization Info) (認証用文字列)
Amazon Route53 で「ドメインの移管」を実施する
Amazon Route53の「ドメイン」画面で、「ドメインの移管」を選択します。
移管したいドメインを検索し、カートにいれます。
「認証コードとネームサーバー」に先ほど取得した認証鍵を入力します。
「ネームサーバーのオプション」は「ドメインと同じ名前のRoute53ホストゾーンからネームサーバーをインポートする」を選びます。
「ホストゾーン」へ予め設定した(対象ドメイン名)のホストゾーンを選択します。
「1. ドメインのお問い合わせ詳細」に管理者情報を再度入力します。このタイミングでAmazonによる代理情報を設定することができます。
登録後、AWSから管理者メールアドレスの確認メールが届くのでリンクをクリックして確認します。
さらにしばらくすると移行元のValue-Domainから移行確認メールが届くのでメール内の認証ページへアクセスして承認します。今回は2ー3時間後に届きました。
その後、Route53で「ドメイン」を確認すると移管が終了した状態になっていました。
whois (移管したドメイン名)を確認し、amazonの情報になったことを確認して完了です。
間に会社間の手続きが入るのでもっと時間がかかるかと思っていましたが、思った以上にスムーズ・短時間で移管できました。
- 投稿日:2020-08-30T20:26:27+09:00
CloudFormationで作成するEC2が指定した起動テンプレートの設定で上書きされない
事象
CloudFormation(以下CFn)を用いて、起動テンプレートからEC2 インスタンス(以下EC2)を作成する際に、起動テンプレートの設定で上書きされませんでした。
なお、CFnを用いず、マネジメントコンソール(以下マネコン)にてテンプレートからEC2を起動する場合は、問題無く設定内容が反映されます。以下画像のような設定で起動テンプレートを作成します。
※起動テンプレート自体はCFnで作成してもマネコンから作成しても変わりません。
CFnでこの起動テンプレートを指定して作成したEC2の設定は以下画像のようになります。
実際にできたEC2と起動テンプレートで設定内容が異なっています。
使用したCFnテンプレート
起動テンプレートとEC2を同時に作成する場合AWSTemplateFormatVersion: "2010-09-09" Description: "Template Settings Overwrite Test" Parameters: ImageId: Default: "ami-0cc75a8978fbbc969" MinLength: 1 Type: "String" KeyPairName: MinLength: 1 Type: "AWS::EC2::KeyPair::KeyName" SubnetIdForEth0: MinLength: 1 Type: "AWS::EC2::Subnet::Id" SecurityGroupIdsForEth0: MinLength: 1 Type: "List<AWS::EC2::SecurityGroup::Id>" Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "Basic Configuration" Parameters: - "InstanceType" - "ImageId" - "KeyPairName" - Label: default: "NetworkInterface Configuration For Eth0" Parameters: - "SubnetIdForEth0" - "SecurityGroupIdsForEth0" Resources: EC2LaunchTemplate: Type: "AWS::EC2::LaunchTemplate" DeletionPolicy: "Delete" Properties: LaunchTemplateName: "test_EC2LaunchTemplate" LaunchTemplateData: BlockDeviceMappings: - DeviceName: "/dev/xvda" Ebs: DeleteOnTermination: true Encrypted: false VolumeSize: 8 VolumeType: "gp2" CreditSpecification: CpuCredits: "unlimited" DisableApiTermination: true EbsOptimized: true ImageId: Ref: "ImageId" InstanceInitiatedShutdownBehavior: "terminate" InstanceType: "t3.nano" KeyName: Ref: "KeyPairName" Monitoring: Enabled: true NetworkInterfaces: - AssociatePublicIpAddress: true DeleteOnTermination: true DeviceIndex: 0 Groups: Ref: "SecurityGroupIdsForEth0" SubnetId: Ref: "SubnetIdForEth0" TagSpecifications: - ResourceType: "instance" Tags: - Key: "Name" Value: "test" - ResourceType: "volume" Tags: - Key: "Name" Value: "test-root" EC2Instance: Type: "AWS::EC2::Instance" Properties: LaunchTemplate: LaunchTemplateId: !Ref 'EC2LaunchTemplate' Version: !GetAtt 'EC2LaunchTemplate.LatestVersionNumber' Outputs: EC2LaunchTemplate: Value: !Ref 'EC2LaunchTemplate' Export: Name: !Sub '${AWS::StackName}-LaunchTemplateID'上のCFnテンプレートで作成した起動テンプレートを別スタックで指定してEC2を作成する場合AWSTemplateFormatVersion: "2010-09-09" Description: "Overwriting test of template settings to be imported" Parameters: EC2LaunchTemplateStackName: MinLength: 1 Type: "String" Resources: EC2Instance: Type: "AWS::EC2::Instance" Properties: LaunchTemplate: LaunchTemplateId: Fn::ImportValue: !Sub '${EC2LaunchTemplateStackName}-LaunchTemplateID' Version: "1"マネコンで作成した起動テンプレートを指定してEC2を作成する場合AWSTemplateFormatVersion: "2010-09-09" Description: "Overwriting test of template settings to be imported" Resources: EC2Instance: Type: "AWS::EC2::Instance" Properties: LaunchTemplate: LaunchTemplateId: "lt-xxxxxxxxxxxxxxxxx" Version: "1"
AWS::EC2::Instance ドキュメントの
LaunchTemplate
項目には以下記載があります。インスタンスの起動に使用する起動テンプレート。AWS CloudFormation テンプレートで指定したパラメータはすべて、起動テンプレート内の同じパラメータを上書きします。
記載通りならEC2を作成するCFnで起動テンプレートを指定した場合は、設定内容が上書きされる筈なので、完全に謎です。
ただし、全ての項目が上書きされないわけではなく、以下の項目が上書きされないようです。
- DisableApiTermination(終了保護)
- EbsOptimized(EBS 最適化インスタンス)※
- InstanceInitiatedShutdownBehavior(シャットダウン動作)
- Monitoring(CloudWatch モニタリングの詳細)
※EBS 最適化インスタンス項目については、デフォルトでEBS最適化がサポートされるインスタンスタイプの場合、false指定であってもEBS最適化が適用されます。false にすると マネコン上の表示やawscliで参照した結果は false になるもののEBS最適化は適用状態となります。
結論
AWSサポートに問い合わせたところ、不具合との事でした。
ご連絡いただきました事象が再現することを確認いたしました。
CloudFormation によるパラメータの暗黙的な上書きが発生していることが分かりました。
なお、この動作につきましてはドキュメント化されていない動作となり、将来的に変更される可能性も考えられます。
AWSサポート回答より※2020/08/27時点回避策
現状では、EC2を作成するCFnテンプレートのAWS::EC2::Instanceリソース側で、明示的にプロパティを指定すれば起動テンプレートを指定していても任意の設定が可能です。
例えば、以下のようにCFnテンプレートを作成すれば、終了保護がTrueになります。
マネコンで作成した起動テンプレートを指定してEC2を作成する場合AWSTemplateFormatVersion: "2010-09-09" Description: "Overwriting test of template settings to be imported" Resources: EC2Instance: Type: "AWS::EC2::Instance" Properties: LaunchTemplate: LaunchTemplateId: "lt-xxxxxxxxxxxxxxxxx" Version: "1" DisableApiTermination: true # 終了保護設定項目を追加
- 投稿日:2020-08-30T19:29:31+09:00
【AWS】EBSまとめてみた
EBS(Elastic Block Storage)とは
EBSとはEC2にアタッチされるブロックレベルのストレージサービス
特徴
- OSやアプリケーション、データの起きばなど様々な用途で利用される
- EC2とはネットワーク接続されている
- 99.999%の可用性を持ち、サイズは1G~16TBでサイズと利用期間で課金される
- ボリュームデータはAZないで複数のHWにデフォルトでレプリケートされており、冗長化不要
- データは永続的に利用可能
- EC2インスタンスは他のAZ内のEBSにアクセスはできない
- 1つのEBSを複数のインスタンスで共有することはできない
- 同じAZのインスタンスのみ付け替えが可能
スナップショット
- EBSはスナップショットにてバックアップ可能
- スナップショットからEBSを復元する際は別AZ、別リージョンにも可能
- スナップショットはS3に保存される
- 増分バックアップ方式
- スナップショットに容量で課金される
EBSのボリュームタイプ
EBSのボリュームタイプは大きくSSD型とHDD型、マグネティックの二つがある。それぞれ詳しくみていく。
汎用SSD
SSDをベースとした汎用的なボリュームタイプ。ユースケースとしては、
- 開発環境
- 小-中規模のデータベース
に使用される。
サイズは、1G~16TBまで保存可能。10000IOPS/ボリュームまで容量に応じたベースライン性能がある。1TB未満のボリュームには、一時的なIOPSの上昇にも対応できるようにバースト性能が用意されている。プロビジョンドIOPS
EBSの中でも最も高性能なSSDをベースとしたボリュームタイプで、高いIOPS性能が求められる場合に利用する。ユースケースとしては、
- 高いIOPSを必要とするNoSQLやアプリ
- 大規模DB
に使用される。
最大64000IOPS/ボリュームまで容量に応じたベースライン性能がある。スループット最適化HDD
HDDをベースとしたスループット重視のボリュームタイプユースケースとしては、
- ビックデータ処理
- 大規模なETL処理やログ分析
に使用される。
スループットを性能指標としており、1TB当たり40MB/秒、最大スループットはボリューム当たり500MB/秒のベースライン性能がある。Cold HDD
最も低コストなボリュームタイプで利用頻度があまりなく、性能もあまり高くないボリュームタイプ。ユースケースとしては
- ログデータやアクセス頻度が低いデータ
- バックアップやアーカイブ
に使用される。マグネティック
旧世代のボリュームで基本使用しない。
- 投稿日:2020-08-30T18:45:15+09:00
【AWS】EC2改めてまとめてみた
EC2とは
AWSが提供する仮想サーバ
特徴
- 起動・停止・削除・マシンスペック変更がボタン一つで数分で可能
- 使って分だけ課金される従量課金制
- インスタンスという単位でサーバが管理される
- 汎用的なIntelアーキテクチャを採用
- Window、Linuxなど多様なOSをサポート
- OSより上の層はユーザ自身でカスタマイズ可能
- 独自のAmazon Machine Image (AMI)にOS設定を作成し、保存して再利用が可能
- 数分で起動可能・スペック変更可能なため、必要に応じてインフラリソースを短時間で調達することが可能。インスタンスタイプ
CPU、メモリ、ストレージ、ネットワークキャパシティーなどサーバーリソースのこと。100以上の中から自身が使用するワークロードに最適なインスタンスタイプを選択する。
インスタンスファミリー
メモリ、I/O、CPUクロック重視、GPU、FPGA搭載などの特徴を持ち大きく5つのカテゴリに分類される。
例) T2.micro
T:インスタンスファミリー
2:世代
micro:インスタンスサイズ
カテゴリ 該当するインスタンスファミリー 汎用 T、M メモリ最適化 R、X 高速コンピューティング P、G、F ストレージ最適化 I、D、H コンピューティング最適化 C インスタンスタイプの選び方の参考記事
https://pages.awscloud.com/rs/112-TZM-766/images/C2-07.pdf
業務でなぜこのインスタンスタイプを使用するのだろう?と思うことがあったので、この記事をみてなるほどと思いました。インスタンスの利用形態
オンデマンドインスタンス
従量課金制の一般的なインスタンス利用形態
リザーブドインスタンス
長期間利用することを約束することで割引を受けることができるインスタンス利用形式。オンデマンドインスタンスと比べて最大75%割安になる。ユースケースとしては、使用量が一定または予測可能なワークロード、アプリケーションの場合に使用する。
スタンダード コンバーティブル 利用期間 1年(40%割引)/3年(60%割引) 1年(31%割引)/3年(54%割引) AZ/インスタンスサイズ/ネットワークタイプ変更可否 有 有 インスタンスファミリー/OS/テナンシー/支払オプション変更可否 なし 有 売却可能か 可能 今後可能になる予定 スポットインスタンス
予備のコンピューティング容量を、オンデマンドインスタンスに比べて(最大90%)割引で利用できるインスタンス利用形式。一時的なオートスケール用、短時間のスペック増加に使うといい。
特徴
- 入札形式で利用するためとても安い
- 起動に通常よりも時間がかかる
- 予備用のため途中で削除される可能性がある。Saving Plan
1〜3年の期間に一定の使用量を守ることによりコストを削減する
特徴
- 1年または3年の期間に特定の処理能力(USD/時間)を使用する契約を結ぶことで適用される割引契約
- AWSコンピューティング使用料金を最大72%節約できる
- EC2、Fargate、AWSLambdaで適用可能物理対応可能なインスタンス
物理サーバにインスタンスを起動して制御が可能なタイプ
ハードウェア専用インスタンス
- 専用HWのVPCで実行されるEC2インスタンス
- ホストHWレベルで他のAWSアカウントに属するインスタンスから物理的に分離する
- 同じAWSアカウントのインスタンスとは共有する可能性がある
Dedicated Host
- 完全に専用として利用できる物理サーバ上でEC2インスタンスを稼働できる
Bare Metal
- OSより下のレイヤーを操作できる
ストレージ
EC2で直接利用するストレージは不可分なインスタンスストアと自分で設定するEBSの2つがある
インスタンスストア
- EC2と同じサーバーを使用する形式。EC2と不可分のブロックレベルの物理ストレージ
- EC2の一時的なデータが保持され、停止、終了とともに削除される
- 無料
Elastic Block Storage(EBS)
- ネットワークで接続されたブロックレベルのストレージでEC2とは独立して管理される
- ハードディスク代わりとして利用する
- EC2を削除してもEBSは削除されない設定が可能で、snapshotをS3に保持可能
- EBS使用料金がかかる
- 投稿日:2020-08-30T18:31:45+09:00
WSL2を使ったDokcerで、`Aws::S3::Errors::RequestTimeTooSkewed` のエラー対処法
環境
- WSL2 (Windows 10 Home)
- Docker for Windows
エラー
Docker上で、AWSと連携しようとしたときに、
Aws::S3::Errors::RequestTimeTooSkewed: The difference between the request time and the current time is too large.のエラーが出た
原因
WSL2で動くDockerコンテナの時刻が大幅にずれているのが原因
Windows ホスト側
$ data Sun Aug 30 18:16:29 JST 2020コンテナのシェル内
$ data Sun Aug 28 11:16:29 JST 2020解決法
はじめに
Aws::S3::Errors::RequestTimeTooSkewed
のエラーはTimezoneが違う場合でも発生するので、
docker-compose.yml 内でtimezone をJSTに設定しとく。docker-compose.ymlservices: web: environment: TZ: Asia/Tokyoつぎに
WSL2の時刻を
data --set
コマンドで同期させる。
まず、コンテナのシェル内でdataコマンドを使うときに、operation not permitted
にならないように、権限設定をしとく必要がある。docker-compose.ymlservices: web: privileged: trueつぎに
コンテナのシェル内で、
data
コマンドを使って日付を変更する$ data --set "2020-08-30 18:16:29"で完了。
他に試したこと
hwclock -s
を用いて無理やり時間を変えれるらしい。-> 下記のエラーが出て、少し解決しようとしてみたが、謎すぎて諦めた
$ hwclock -s hwclock: Cannot access the Hardware Clock via any known method. hwclock: Use the --debug option to see the details of our search for an access method参考リンク
- 投稿日:2020-08-30T18:31:37+09:00
[Python × AWS × Serverless] PyCon JP 2020 の登壇で言い残したこと
Python × AWS × Serverless 初学者が次の一歩を踏み出すためのテクニック
というタイトルで登壇してきました。社外の登壇は超久々でとても緊張しました。
当初考えてた内容から路線変更したりとか、人はなぜ締め切りをめいっぱい使ってしまうのか、とか、色々考えたりしながらなんとか形にはなりましたが、構成や個別のトピックとか思い返せばまだ言い足りないことがあったなと思いましたのでここで補足しようと思います。
スライドは SpeakerDeck で公開しています。
スライドタイトルについて
内容的に Tips の紹介という趣のスライドなので、「これで初学者を抜け出せるのか?」って疑問にはハッキリ回答できてないかもなぁ、、、と感じました。
もちろんタイトル詐欺の意図はありません。自分なりにこのタイトルと内容を選んだ背景が(一応は)ありますので記しておきます。
私のバックグラウンドですが、AWS方面は割と知見があるものの、アプリ開発のがっつりした経験は持っていない、といったスキルセットです。このような背景があって、基本的なディレクトリ構成とか、どうやって設計していくかとか、そのへんの慣れがなく戸惑っていた側面が大きかったんです。で、わからないがゆえに「サーバーレスに固有な何かがあるのでは?」みたいな不安も出てきて、どうするのが良いのかわからなかったです(自覚的ではなかったのでなかなか解消できませんでしたが)。
その後、(本発表でも喋ったように)サーバーレスといえどアプリ開発部分の考え方は普通に言語のプラクティスに従えば良いとわかりました。これからサーバーレスに挑戦される皆さまには、必要以上に恐れることないよということが伝わればいいなと。
私自身も、以前はサーバーレスについて「次何したらいいのかわからなくてモヤモヤする」状態を経験していました。そのときに知りたかった情報というのが今回のネタだった、ということで発表に至っております。
このような前置きの共有って地味に大事な部分ですよね。共感を得られるストーリーとか Why が提起されないプレゼンって訴求力がないので。ここを省いてしまったことで、視聴者のみなさまに訴求するメッセージがわかりにくくなってしまったかもしれないなと、大いに反省しました。
AWS サービスのリソース名を明示的に与える
「ステージ」云々のセクションで説明を入れそびれた部分です。
Serverless Framework は、デプロイするAWSリソースの名前をステージ間で衝突しないようによしなに命名してくれます。よって、 Serverless Framework を使っていれば多くの場合 Name 属性は必須ではありません。
しかし、あえて「リソース名は明示的に命名した方がいいんじゃないかな?」というのがここで述べたいことです。スライドにも実はちょろっと出てきますが、命名規則というのは例えばこういうものです↓
# template.yml # SQS Queue name Name: ${self:service}-my-queue-${self:provider.stage}こういう命名にすることでサービス間/ステージ間で名前が被らない・予測しやすいリソース名管理ができます。
反対意見として、「インフラは全部コードで管理していくんだから、人間が見るための名前に気を遣う必要なくない?」とする考え方もあります。私もどっちかと言えばそっちを支持したい派です。
リソース名の衝突や、文字数制限 (例: lambda の関数名は64文字まで) などの問題を気にしなくて済む分、自分で変に命名しない方が楽だったりします。命名規則の管理も、(スタックの)ソースの記述が増えてうるさくなっちゃいいますし。できるなら名前はおまかせの方が嬉しいんです。
では、何故明示的な命名をした方がよいのでしょうか。個人的にうれしいポイントは2つあって、それぞれ以下の通りです。このへんは自分が受け持ってる案件の状況がバイアスに入ってると思います。たぶん。
Lambda のログを検索しやすい
Step Functions をよく利用するので、複数の Lambda にまたがってログを検索したいシーンがちょいちょい出てきます。
ログの検索にはいまのところ CloudWatch Logs Insights を使うケースがほとんどなのですが、その時のロググループの名前が(明示的に命名規則を付けていると)スッキリしますので、検索がちょっとだけやりやすいです。
おそらく、Datadogを利用しつつログ検索はタグで絞るようにするとか、(命名規則に頼らずとも)色々やりようはあるんだろうと思うのですが、今自分が採れる選択の中ではこのやり方が運用シーンにも即しておりそこそこ妥当性のあるやり方かなと思っています。
※このへんは周辺ツール (CLI や Datadog など)をもうちょっと充実させれば景色も見解も変わってくると思います
リソース名を参照する時に見通しがよく、保守しやすい
スタックの記述とアプリ側の記述、どっちもある程度スッキリ書くためには命名規則はあった方がメリットが大きいと思います(より主張したいメリットはこっちです)。
シンプルな構成図なら CloudFormation の GetAtt やら Ref やらで値を引いてやればいいので、さほど問題じゃありません。命名規則が役立つのは、構成図が膨れてきた頃合いです。
例えば、SQS のキューを複数作っている構成を想像してみます。Python のコード上ではキューのエンドポイント、すなわち Queue URL (AccountId, Region, QueueName の3つによって特定可能) が必要です。
作成したキューの URL を Ref で参照して lambda 環境変数に渡してあげてもよいのですが、個人的にはアカウントID、リージョン、キューの Name 属性をそれぞれ渡してあげれば十分かな、と思います。
アカウントIDやリージョンは他のAWSサービスを利用する場合にも出番がありそうです。あって困るものではなさそうですし、Queue URL を組み立てるユーティリティ関数の実装もごく簡単ですので、小回りが効きそうな方を選択するのが良いだろうと考えています。
Python のコードベースとしての管理は上記の通りとして、今度はスタック側の管理を考えてみます(どちらかと言えば、命名規則のメリットが大きいのはこちらだと考えます)。
さっきの SQS のキュー名の定義を再掲します。キューの名前は、
service
とstage
を join して作るイメージでした。# 【再掲】 template.yml # SQS Queue name Name: ${self:service}-my-queue-${self:provider.stage}実際のスタック定義としては、上記の Name の文字列全体(あるいは
my-queue
の部分)をテンプレートの環境変数 or custom セクションで宣言してあげる感じになります。このやり方をすることで、他所のリソース(例えば IAM ポリシーの定義)からの参照が比較的スッと書けます。名前の部分は変数として参照可能な宣言を行っているので、 ARN などは単に join して組み立てるだけです。
ここで「いやいや、 Ref とか GetAtt とかあるじゃん」という指摘があると思いますが、それらを迂闊に使うのは個人的にあまりおすすめできません。
特にポリシー周りにおいて、これらを濫用すると CloudFormation の循環参照が起こりやすい気がします。このへんは経験則によるところが大きく明確な文章で根拠を示せませんが、私としては軽々しく AWS リソースを直接参照する関数を使いたくないんですよね...。代えて、依存先として害が少ない環境変数や、 custom セクションの変数に依存させる戦略を採っています。意識的に(特にIAMポリシー周りでの) Ref, GetAtt は使用を控えめにしています。
ただでさえ CloudFormation のデバッグは苦行なのに、構成が大きくなってから後で CloudFormation の循環参照問題に頭抱えるハメになるの、イヤですよね...? あれは人類がデバッグするものじゃないです...
このような理由があって、私はリソース名を(多少冗長で面倒であったとしても)明示的に宣言するのが好みです。この節で書いたことはあくまで副次的な効果ではありますが、私としては結構重く見ています。
おわり
サーバーレスのネタって、概要をさらう資料やチュートリアルをこなす程度の情報なら割とそのへんに転がってるのですが、結局それだけでは実際の開発シーンにおいて痒い部分を解消してくれない実感があったので、そのへんを埋めたい思いで応募してみました。
今の私が伝えたいことはおおよそ喋れたと思いますが、AWS周りの基礎知識はもっと深堀りできる部分だったので45分セッションで応募してみても良かったかもしれません(Python 成分が薄くなっちゃうので TPO 的にどうなの、とは思います)
一方で、残念ながら思ったより反応が少なめだったので、今回の話自体に需要があったのかどうか、今回の発表からはいまひとつ感触がつかめないところではありました(裏番組のセッションが強すぎた点、私の発表自体も改善余地があった点、PyCon の主要な参加層にはミートしづらいネタだった、など考えうる要因は複数あります)。需要皆無ということはおそらくないはずなので、場を変えて喋ってみるなどして探っていこうと思います。ベースの資料はできたことですし。
実践寄りな知識をもうちょっと体系立てて書けたら良いなとも思います。次回の技術書典で頑張ってみるというのも良いかもしれませんね。
- 投稿日:2020-08-30T15:24:30+09:00
[初心者向け] 常に小さくテストすることを心がけよう
概要
コードを書いていて、思うように動かない。どう書けばいいかわからない。困った。
そういうことはないだろうか?試行錯誤を繰り返して動作確認する必要があることは時々ある。そんなときに、大きなproductionコードを動かしながらtry and errorしていたりしないだろうか?
「時間がないから」と言い訳し別の方法を検討する事を拒否して、production上で何度も何度も実行しながら「遅い!」と文句を言っていないだろうか?
大きなコードで動作確認を繰り返すと無駄に時間を消費するし、それだけでなく確認したいそもそもの対象以外から影響を受けて、次第に自分が何を確認しているか分からなくなってくる。
【動作確認は可能な限り絞ったサンプルを使ってテストするべきだ。】
【言語にシェル環境があれば、シェル環境を利用することを勧める。】
そんな話をしたい。
あるケースの場合
あるバッチ処理で、大きなcsvファイルをpythonで処理してs3に格納する必要があった。
そのため、最終的にpandasが持つ大きなデータをboto3を用いてアップロードする方法が問題になった。オリジナルコード
s3 = boto3.client('s3'); // df : pandas.DataFrame s3.put_object(df.to_csv().encode(), bucket, key)この場合、to_csv()が全データを文字列化して出力し、さらにそれをencode()が
bytes配列化して出力する。つまり、dfが持つデータが巨大になるとその数倍の速度でメモリを食いつぶしていた。これをstream処理にしたい。
stream処理?
巨大なデータを全てオンメモリに展開するのではなく、一部ずつ処理していく事によりメモリ消費量を減らすことができる。一般的にこのような処理をstream処理と呼ぶ。
pythonのstream処理
stream処理を行う方法には、generatorを使う方法とioパッケージを使用する方法がある。
データストリーム
https://docs.python.org/3/library/io.html
ioパッケージには、io.BytesIOやio.StringIOというオンメモリストリーム処理をするためのクラスが用意されている。
class 意味 io.BytesIO バイトストリーム io.StringIO 文字列ストリーム io.TextIOWrapper 文字列IOラッパー(バイトストリームを文字列ストリーム化する) このあたりのクラスを使用する。
論理処理(クラスなど)のストリーム
generatorは、多くの論理ブロックを順次処理するときに用いる。
例えば大量の行を持つcsvを、一行ずつ処理したい場合、全ての行を一度に読み込むのではなくブロックごとに読みながら処理できる。generatorをここでは説明しないが、pythonで初心者を脱するためにはgeneratorの知識は必須だ。
実際にstream化してみよう!
今回やりたいのはデータ処理のストリーム化なので、ioパッケージの話。
$ python Python 3.6.8 (default, Dec 5 2019, 17:44:50) [GCC 7.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>>コマンドラインでpythonと打ち込むと、pythonシェルが起動する。
ここで調査を行う。>>> import pandas >>> import boto3 >>> import io今回動作確認に利用するパッケージはこの3つ。
まず検索したところ、以下で動きそうな雰囲気だった。>>> df = pandas.DataFrame([1,2,3]) >>> sio = io.StringIO() >>> df.to_csv(sio) >>> s3 = boto3.client('s3') >>> s3.upload_fileobj(sio, backet, key)
s3.upload_fileobj
はs3でstreamingにアップロードするための関数らしい。
実行したところ、s3上にファイルはできていたが、ファイルのサイズは0だった。
調べたところ、upload_fileobjがbyte streamでなければならないようだ。そこで、StringIOではなくBytesIOを使ってみる。
>>> bio = io.BytesIO() >>> df.to_csv(bio) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/kishibashi/pandas/pandas/core/generic.py", line 3167, in to_csv formatter.save() File "/home/kishibashi/pandas/pandas/io/formats/csvs.py", line 206, in save self._save() File "/home/kishibashi/pandas/pandas/io/formats/csvs.py", line 314, in _save self._save_header() File "/home/kishibashi/pandas/pandas/io/formats/csvs.py", line 283, in _save_header writer.writerow(encoded_labels) TypeError: a bytes-like object is required, not 'str'to_csvがBytesIOにstrを渡したために、BytesIOが怒った。
つまり、to_csvからテキストストリームで受け取りつつ、upload_fileobjにバイトストリームで渡す必要がある。このようなストリームの変換に使うのが
io.TextIOWrapper
というクラスだ。使ってみよう。>>> bio = io.BytesIO() >>> w = TextIOWrapper(bio)bioやwにwriteしたりreadしながらどう動くのか確認して以下の事がわかった。
TextIOWrapperはBytesIOをラップしてくれて、StringIOと同じ機能をもつ。そして、bioとwは入力と出力のような方向性をもっていない。bioにbytesを書き込むとbioからもwからも読み出せるし、wにテキストを書き込むとwからもbioからも読み出せる。つまりこうなる。
>>> bio = io.BytesIO() >>> w = TextIOWrapper(bio) >>> df.to_csv(w) >>> w.seek(0) >>> s3.upload_fileobj(bio, bucket, key)これでs3上のファイルにデータがアップロードされることが確認できた。
ちなみにseekは読み始める位置を最初に変更するおまじないだ。
伝えたい事
この文章で伝えたい事は、ここに書いてあるstreaming処理の詳細ではない。
【分からないことは動作確認するのが理解の早道であり、動作確認はできるだけ小さくしよう】
ということ。
上記のpython shell上の動作確認では、pandas、io、boto3以外なにもない。
どこからpandasに与えるcsvデータを持ってくるのか、サーバーがdjangoで動いているかどうかなど、productionにありがちな外部環境は、この動作確認には全く関係のない事だ。
- shell上での確認
- 単体テストを書いて確認
- jupyter notebookのような環境に慣れているならそちらでも
プロダクションコード上での動作確認は時間の無駄なので絶対にしないように。
(もちろん、少し修正すれば動く事が分かっているならその限りではない)他の言語のシェル環境は?
JavaScript
$ node Welcome to Node.js v12.13.1. Type ".help" for more information. >Golang
標準では用意されていない模様。
Java
JDK9からJShellというモノが追加されている模様。使ったことはない。
- 投稿日:2020-08-30T12:37:30+09:00
Rails 6 で omniauth-cognito-idp を使って Amazon Cognito 認証を実装するサンプル
要点
Amazon Cognitoを使えば、deviseに依存せずに、ユーザ登録・ログイン・ログアウト・パスワードリセット・ソーシャルログイン・n段階認証・SAMLなど、様々な機能がアプリケーションコードから分離するかたちで追加可能です。Amazon Cognitoの他にも、Auth0やFirebase Authentication等があり、IDaaS(Identity as a Service)と呼ばれます。
本記事では、Railsアプリケーションで動作検証をする場合の手順を記載しています。
- Amazon Cognitoのユーザープールの作成とクライアントの設定の基本的な手順を解説(作業時間: 10分程度)
- 動作検証するためのサンプルアプリの実装方法を解決(作業時間: 10分程度)
なお、記事中のスクリーンショットに記載されたクライアントIDやシークレットは、既に削除されているため利用することはできません。ご自身で設定して動作検証してみてください。
環境
% ruby -v ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux] % bin/rails version Rails 6.0.3.2手順1: ユーザープールの作成とクライアントの設定
作業時間: 10分程度
Amazon Cognito のコンソールから「ユーザープールを作成する」を押下
プール名を入力
サンプルなのでデフォルトを確認するを押下
プールの作成
作成完了
プール ID
をメモする:ドメイン名を設定
ナビゲーションの
ドメイン名
をクリックし、認証フォームのURLを設定。設定した、https://{your prefix}.auth.ap-northeast-1.amazoncognito.com
をメモする:アプリクライアントの作成
ナビゲーションの
アプリクライアント
をクリックし、アプリクライアントの追加
をクリック:
アプリクライアントID
とアプリクライアントのシークレット
をメモ:アプリクライアントの設定
ナビゲーションの
アプリクライアントの設定を押下
し、コールバックURLとサインアウトURLを設定(それ以外の設定はスクリーンショットを参照):これでcognito側の設定は完了です(なお、スクリーンショットの値は利用できません)。
手順2: アプリケーションの実装
作業時間: 10分程度
rails new
サンプルなので割愛
omniauth-cognito-idp を追加
gem 'omniauth-cognito-idp'クレデンシャルの追加
今回はサンプルなので
development
で、ユーザプール作成時に生成した諸々をセットする:$ bin/rails credentials:edit -e developmentaws: access_key_id: 123 secret_access_key: 345 # 上記でメモした「ドメイン名」に含まれるリージョン region: ap-northeast-1 # 上記でメモした「アプリクライアントID」 cognito_client_id: *****YOUR CLIENT ID***** # 上記でメモした「アプリクライアントのシークレット」 cognito_client_secret: *****YOUR SECRET***** # 上記でメモした「ドメイン名」 cognito_user_pool_site: https://*****YOUR PREFIX*****.auth.ap-northeast-1.amazoncognito.com # 上記でメモした「プールID」 cognito_user_pool_id: *****YOUR POOL ID*****イニシャライザの追加
# config/initializers/omniauth.rb Rails.application.config.middleware.use OmniAuth::Builder do provider( :cognito_idp, Rails.application.credentials.aws[:cognito_client_id], Rails.application.credentials.aws[:cognito_client_secret], client_options: { site: Rails.application.credentials.aws[:cognito_user_pool_site] }, scope: 'email openid', user_pool_id: Rails.application.credentials.aws[:cognito_user_pool_id], aws_region: Rails.application.credentials.aws[:region], ) endHomeControllerの実装
root で表示する画面:
$ bin/rails g controller home show --skip-assets --skip-helper<h1>Home#show</h1> <p>Find me in app/views/home/show.html.erb</p> <h1>RoR Cognito Sample</h1> <p>Step 1 - Login.</p> <%= button_to 'Login', 'auth/cognito-idp', method: :post %># config/routes.rb Rails.application.routes.draw do root 'home#show' endCognitoIdpControllerの実装
OmniAuthで認証後の処理をするコールバック:
$ bin/rails g controller cognito_idp --skip-template-engine --skip-assets --skip-helperclass CognitoIdpController < ApplicationController def callback # 実際は provider と uid を使って、Userレコードを作成/参照し、user_id をsessionにセットするが # サンプルなのでauth hashをセット session[:userinfo] = request.env['omniauth.auth'] redirect_to '/dashboard' end end# config/routes.rb Rails.application.routes.draw do root 'home#show' get 'auth/cognito-idp/callback' => 'cognito_idp#callback' endセッションストアでCookieを利用して
session[:userinfo]
をセットしようとすると最大容量を超過するので、セッションストアをメモリに変更:# config/initializers/session_store.rb Rails.application.config.session_store :cache_store
config/environments/development.rb
にconfig.cache_store = :memory_store
を追加。ApplicationControllerに認証に関する振る舞いの追加:
実際はcurrent_userなども実装するがサンプルなのでuserinfoがsessionにセットされているかどうかを確認:
class ApplicationController < ActionController::Base private def authenticate_user! return redirect_to(root_path) unless signed_in? end def signed_in? session[:userinfo].present? end helper_method :signed_in? endDashboardControllerの実装
ログイン後の画面を実装する:
$ bin/rails g controller dashboard show --skip-assets --skip-helperclass DashboardController < ApplicationController before_action :authenticate_user! def show end end# config/routes.rb Rails.application.routes.draw do root 'home#show' get 'auth/cognito-idp/callback' => 'cognito_idp#callback' get 'dashboard' => 'dashboard#show' end<h1>Dashboard#show</h1> <p>Find me in app/views/dashboard/show.html.erb</p> <p><%= session[:userinfo].inspect %></p> <p><%= link_to 'logout', logout_path, method: :delete %></p>SessionsControllerの実装:
ログアウト処理を実装(cognito側もログアウトする必要がある):
$ bin/rails g controller sessions --skip-template-engine --skip-assets --skip-helperclass SessionsController < ApplicationController before_action :authenticate_user! def destroy reset_session redirect_to cognito_logout_url end enddirect ルーティングを使って、cognito側のログアウトを実装:
# config/routes.rb Rails.application.routes.draw do root 'home#show' get 'auth/cognito-idp/callback' => 'cognito_idp#callback' get 'dashboard' => 'dashboard#show' delete 'logout' => 'sessions#destroy' direct :cognito_logout do query = { client_id: Rails.application.credentials.aws[:cognito_client_id], logout_uri: root_url, }.to_param "#{Rails.application.credentials.aws[:cognito_user_pool_site]}/logout?#{query}" end end動作検証
rails server
を起動してlocalhost:3000
にアクセス:ルートを表示
Login
を押下:cognitoのログインフォームが表示されるのでサインアップする
メールアドレスを確認して認証コードを入力するとログインが完了します。
参考
- Amazon Cognito のドキュメント
- Sage/omniauth-cognito-idp: OmniAuth Strategy for AWS Cognito in Ruby
- Auth0 Ruby On Rails SDK Quickstarts: Login
以上
- 投稿日:2020-08-30T11:54:36+09:00
Amazon ECRがOCI ArtifactをサポートしてHelm chartsも管理できるようになったよ
はじめに
2020/8/28 に Amazon ECR が OCI Artifact をサポートしました。
これにより Helm charts や OPA Bundles など、コンテナイメージ以外の
コンテンツタイプ (アーティファクト) を ECR で保存、配布できるようになります。OCI Artifact Support In Amazon ECR
https://aws.amazon.com/jp/blogs/containers/oci-artifact-support-in-amazon-ecr/例えば組織内で安全に Helm charts を共有しようとした場合に
これまでは ChartMuseum 等を使用して、プライベートな
Helm chart registry を構築する必要がありましたが、ECR だけで管理できるようになります。8/17の AWS Container Day at KubeCon で Developer Preview として発表されたばかりだった
認識ですが、2週間もたたずに正式リリース?となったようです。
今回のローンチの対象は SDK と CLI のサポートで、マネジメントコンソール側の変更は
今年後半にリリース予定とのこと。ほぼドキュメント通りの手順になってしまいますが、やってみます。
作業環境
- Helm: v3.3.0
- AWS CLI: 2.0.43
- Kubernetes: 1.16.5 (Docker Desktop)
Helm のインストール
クライアント端末に Helm 3 がインストールされていない場合はインストールしておきます。
Github のリリースページからバイナリを取得、インストールスクリプトの使用、
パッケージマネージャー経由など複数のインストール方法が準備されています。Installing Helm
https://helm.sh/docs/intro/install/$ helm version version.BuildInfo{Version:"v3.3.0", GitCommit:"8a4aeec08d67a7b84472007529e8097ec3742105", GitTreeState:"dirty", GoVersion:"go1.14.7"}Helm 3 における OCI のサポートは 現在 experimental な機能とみなされているため
HELM_EXPERIMENTAL_OCI
を環境変数に設定する必要があります。export HELM_EXPERIMENTAL_OCI=1
ECR レポジトリの作成と認証
AWS CLI で作成します。
特に追加のオプション等は不要で、いつもどおり作成できます。$ aws ecr create-repository --repository-name helm-test { "repository": { "repositoryArn": "arn:aws:ecr:ap-northeast-1:123456789012:repository/helm-test", "registryId": "123456789012", "repositoryName": "helm-test", "repositoryUri": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test", "createdAt": "2020-08-29T12:22:02+09:00", "imageTagMutability": "MUTABLE", "imageScanningConfiguration": { "scanOnPush": false }, "encryptionConfiguration": { "encryptionType": "AES256" } } }以下のコマンドで Helm クライアントを ECR に認証します。
$ aws ecr get-login-password | helm registry login \ > --username AWS --password-stdin 123456789012.dkr.ecr.region.amazonaws.com Login succeeded
aws ecr get-login
コマンドは非推奨になっており、AWS CLI v2 では削除されているため
aws ecr get-login-password
を使用します。参考:AWS CLIでECRにログインする時はget-loginではなくget-login-passwordを使おう
https://qiita.com/hayao_k/items/3e4c822425b7b72e7fd0Chart の作成
helm create
コマンドで Chart の雛形を作成することができます。$ mkdir helm-test $ cd helm-test $ helm create mychart Creating mychart今回はこの雛形をそのまま利用します。
ECR へ Push
helm chart save
コマンドで Chart ディレクトリをローカルキャッシュに保存します。
この際、リモートの ECR の URI を使用して、エイリアスを作成します。
このあたりは、コンテナイメージを push するときと同じ感覚です。$ helm chart save mychart mychart ref: mychart:0.1.0 digest: c455f9c99430ee099ea66999a40a0978947ccfd76e6922cdb8a911491326b0ed size: 3.5 KiB name: mychart version: 0.1.0 0.1.0: saved $ helm chart save mychart 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart ref: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart digest: 4865989e73df2c14b15ee2a1befa11673e455cc5a728239a05f5bc12b13cadb3 size: 3.5 KiB name: mychart version: 0.1.0 mychart: saved $ helm chart list REF NAME VERSION DIGEST SIZE CREATED 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-te... mychart 0.1.0 4865989 3.5 KiB 28 seconds mychart:0.1.0 mychart 0.1.0 4865989 3.5 KiB About a minute
helm chart push
コマンドで ECR に push できます。$ helm chart push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart The push refers to repository [123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test] ref: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart digest: 4865989e73df2c14b15ee2a1befa11673e455cc5a728239a05f5bc12b13cadb3 size: 3.5 KiB name: mychart version: 0.1.0 mychart: pushed to remote (1 layer, 3.5 KiB total)今回のリリースではマネジメントコンソールに変更が入っていないため、
現在は普通に Docker イメージが格納されているだけのように見えます。push した Chart を確認すると、適切な artifactMediaType で保存されていることがわかります。
$ aws ecr batch-get-image --repository-name helm-test --image-ids imageTag=mychart --query 'images[].imageManifest' [ "{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:65a07b841ece031e6d0ec5eb948eacb17aa6d7294cdeb01d5348e86242951487\",\"size\":141},\"layers\":[{\"mediaType\":\"application/tar+gzip\",\"digest\":\"sha256:acd033c1b722f7dcbfb38a909c422540c584feef52ad4aedb8289eb074638f27\",\"size\":3562}]}" ]ECR から Chart を pull する
pull を確認するため、
helm chart remove
コマンドで ローカルキャッシュ上の Chart を削除します。$ helm chart remove 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart mychart: removed $ helm chart remove mychart:0.1.0 0.1.0: removed
helm chart pull
コマンドで pull します。$ helm chart pull 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart mychart: Pulling from 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test ref: 123456789012.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart digest: 4865989e73df2c14b15ee2a1befa11673e455cc5a728239a05f5bc12b13cadb3 size: 3.5 KiB name: mychart version: 0.1.0 Status: Downloaded newer chart for 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart $ helm chart list REF NAME VERSION DIGEST SIZE CREATED 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-te... mychart 0.1.0 4865989 3.5 KiB 9 hoursChart のデプロイ
pull した Chart を使用して Kubernetes にデプロイするには
helm chart export
コマンドを使用して一度ローカルに export する必要があります。helm chart export 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart --destination ./chart-install ref: 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/helm-test:mychart digest: 4865989e73df2c14b15ee2a1befa11673e455cc5a728239a05f5bc12b13cadb3 size: 3.5 KiB name: mychart version: 0.1.0 Exported chart to chart-install\mychart/
helm install
コマンドで Namespce: helm-test に Chart をインストールします。$ cd chart-install $ kubectl create ns helm-test namespace/helm-test created $ helm install mychart ./mychart -n helm-test NAME: mychart LAST DEPLOYED: Sun Aug 30 11:28:24 2020 NAMESPACE: helm-test STATUS: deployed REVISION: 1 NOTES: 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace helm-test -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=mychart" -o jsonpath="{.items[0].metadata.name}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace helm-test port-forward $POD_NAME 8080:80問題なくデプロイできました。
$ kubectl get all -n helm-test NAME READY STATUS RESTARTS AGE pod/mychart-5965fbff94-xxnd2 1/1 Running 0 32s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/mychart ClusterIP 10.99.155.110 <none> 80/TCP 32s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/mychart 1/1 1 1 32s NAME DESIRED CURRENT READY AGE replicaset.apps/mychart-5965fbff94 1 1 1 32s $ helm list -n helm-test NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION mychart helm-test 1 2020-08-30 11:28:24.6691172 +0900 JST deployed mychart-0.1.0 1.16.0削除は
helm uninstall
コマンドを使用します。$ helm uninstall mychart -n helm-test release "mychart" uninstalled $ kubectl delete ns helm-test namespace "helm-test" deletedChart を ECR から削除する
ECR 上の Chart を削除する手順は、Docker イメージの時と変わりません。
$ aws ecr batch-delete-image --repository-name helm-test --image-ids imageTag=mychart { "imageIds": [ { "imageDigest": "sha256:4865989e73df2c14b15ee2a1befa11673e455cc5a728239a05f5bc12b13cadb3", "imageTag": "mychart" } ], "failures": [] }参考
Pushing a Helm chart
https://docs.aws.amazon.com/AmazonECR/latest/userguide/push-oci-artifact.html以上です。
参考になれば幸いです。
- 投稿日:2020-08-30T10:58:03+09:00
AWS IoT CoreとAzure IoT Hubを両方やってみる
AWS IoT CoreとAzure IoT HubでIoT機器からデータをアップロードするサンプルを試しました。手順の比較
No. AWS IoT Core Azure IoT Hub (事前) - Azure CLIにエクステンションを追加する (事前) - リソース グループを作成する 1 - IoT Hubを作成する 2 モノを作成する デバイスを作成する 3 証明書を作成する - 4 ポリシーを作成する - 5 証明書にポリシーをアタッチする - 6 証明書にモノをアタッチする - 7 送信側のプログラムを作成する 送信側のプログラムを作成する
- AzureはIoT Hubそのものを作成する手順があります。
- 認証方式がAWSはX.509 証明書、Azureは対称キーです。他の認証方式を使うと手順も変わると思います。
AWSの手順
モノを作成する
コマンド一発です。
aws iot create-thing --thing-name Thing001
証明書を作成する
パラメータで出力ファイル名を指定しています。
aws iot create-keys-and-certificate --certificate-pem-outfile "Cert001.cert.pem" --public-key-outfile "Cert001.public.key" --private-key-outfile "Cert001.private.key" --set-as-active
この先の手順で、実行結果に含まれる証明書のARN(
certificateArn
)が必要となりますのでメモっておきましょう。(下の実行結果はAWS CLIのリファレンスの例です)
実行結果
実行結果{ "certificateArn": "arn:aws:iot:us-west-2:123456789012:cert/9894ba17925e663f1d29c23af4582b8e3b7619c31f3fbd93adcb51ae54b83dc2", "certificateId": "9894ba17925e663f1d29c23af4582b8e3b7619c31f3fbd93adcb51ae54b83dc2", "certificatePem": " -----BEGIN CERTIFICATE----- MIICiTCCEXAMPLE6m7oRw0uXOjANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMC VVMxCzAJBgNVBAgEXAMPLEAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6 b24xFDASBgNVBAsTC0lBTSEXAMPLE2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAd BgkqhkiG9w0BCQEWEG5vb25lQGFtYEXAMPLEb20wHhcNMTEwNDI1MjA0NTIxWhcN MTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBhMCEXAMPLEJBgNVBAgTAldBMRAwDgYD VQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDAEXAMPLEsTC0lBTSBDb25z b2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEXAMPLE25lQGFt YXpvbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMaK0dn+aEXAMPLE EXAMPLEfEvySWtC2XADZ4nB+BLYgVIk60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9T rDHudUZEXAMPLELG5M43q7Wgc/MbQITxOUSQv7c7ugFFDzQGBzZswY6786m86gpE Ibb3OhjZnzcvQAEXAMPLEWIMm2nrAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4 nUhVVxYUntneD9+h8Mg9qEXAMPLEyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0Fkb FFBjvSfpJIlJ00zbhNYS5f6GuoEDEXAMPLEBHjJnyp378OD8uTs7fLvjx79LjSTb NYiytVbZPQUQ5Yaxu2jXnimvw3rrszlaEXAMPLE= -----END CERTIFICATE-----\n", "keyPair": { "PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkEXAMPLEQEFAAOCAQ8AMIIBCgKCAQEAEXAMPLE1nnyJwKSMHw4h\nMMEXAMPLEuuN/dMAS3fyce8DW/4+EXAMPLEyjmoF/YVF/gHr99VEEXAMPLE5VF13\n59VK7cEXAMPLE67GK+y+jikqXOgHh/xJTwo+sGpWEXAMPLEDz18xOd2ka4tCzuWEXAMPLEahJbYkCPUBSU8opVkR7qkEXAMPLE1DR6sx2HocliOOLtu6Fkw91swQWEXAMPLE\GB3ZPrNh0PzQYvjUStZeccyNCx2EXAMPLEvp9mQOUXP6plfgxwKRX2fEXAMPLEDa\nhJLXkX3rHU2xbxJSq7D+XEXAMPLEcw+LyFhI5mgFRl88eGdsAEXAMPLElnI9EesG\nFQIDAQAB\n-----END PUBLIC KEY-----\n", "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nkey omittted for security reasons\n-----END RSA PRIVATE KEY-----\n" } }
ポリシーを作成する
まずポリシードキュメントを作成します。お試しなのでフルオープンですのでお気をつけください。
policy.json{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iot:*" ], "Resource": [ "*" ] } ] }次にポリシーを作成します。
aws iot create-policy --policy-name Policy001 --policy-document file://policy.json
証明書にポリシーをアタッチする
コマンド一発。証明書のARNは先の手順で取得したものです。
aws iot attach-policy --policy-name Policy001 --target "{証明書のARN}"
証明書にモノをアタッチする
こちらもコマンド一発。証明書のARNは先の手順で取得したものです。
aws iot attach-thing-principal --thing-name Thing001 --principal "{証明書のARN}"
送信側のプログラムを作成する
https://aws.amazon.com/jp/premiumsupport/knowledge-center/iot-core-publish-mqtt-messages-python/
にあるサンプルを使います。AWS IoT SDK for Python v2のインストール
pip install awsiotsdkソースコードの変更
変数名 内容 ENDPOINT *1で取得できます CLIENT_ID モノの名前(Thing001) PATH_TO_CERT CERTのファイルパス(Cert001.cert.pem) PATH_TO_KEY プライベートキーのファイルパス(Cert001.private.key) PATH_TO_ROOT ここのAmazonルートCA 1をダウンロード *1
aws iot describe-endpoint --endpoint-type iot:Data-ATS
コマンド実行結果
20回送信したら終了します。
Connecting to xxxxxxxxxxxxxx-ats.iot.ap-northeast-1.amazonaws.com with client ID 'Thing001'... Connected! Begin Publish Published: '{"message": "Hello World [1]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [2]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [3]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [4]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [5]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [6]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [7]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [8]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [9]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [11]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [12]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [13]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [14]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [15]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [16]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [17]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [18]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [19]"}' to the topic: 'test/testing' Published: '{"message": "Hello World [20]"}' to the topic: 'test/testing' Publish End
Azureの手順
Azure CLIにエクステンションを追加する
一部エクステンションが必要なコマンドがあるため、azure-iotエクステンションをインストールします。
az extension add --name azure-iot
リソース グループを作成する
IoT部分とは直接関係ありませんが、Azureの場合は必ずリソースグループが必要です。
az group create --name iot-resource-group --location japaneast
IoT Hubを作成する
コマンド一発。お試しなのでSKUは
F1
(無料)としてます。az iot hub create --name iot-hub-00001 --resource-group iot-resource-group --partition-count 2 --sku F1
デバイスを作成する
コマンド一発
az iot hub device-identity create --hub-name iot-hub-00001 --device-id Device001
送信側のプログラムを作成する
https://github.com/Azure-Samples/azure-iot-samples-python/blob/master/iot-hub/Quickstarts/simulated-device/SimulatedDevice.py
にあるサンプルを使います。IoT Hub Device SDKのインストール
pip install azure-iot-deviceソースコードの変更
変数名 内容 CONNECTION_STRING *2で取得できます *2
az iot hub device-identity connection-string show --hub-name iot-hub-00001 --device-id Device001
コマンド実行結果
無限に続きます
IoT Hub Quickstart #1 - Simulated device Press Ctrl-C to exit IoT Hub device sending periodic messages, press Ctrl-C to exit Sending message: {"temperature": 22.73671571030419,"humidity": 65.13300283503716} Message successfully sent Sending message: {"temperature": 21.122891449050375,"humidity": 75.35478976197727} Message successfully sent Sending message: {"temperature": 30.11015190710952,"humidity": 79.1313503131281} Message successfully sent Sending message: {"temperature": 29.056883680577876,"humidity": 74.9253608733604} Message successfully sent Sending message: {"temperature": 30.35374671931261,"humidity": 73.57241118544626} Message successfully sent Sending message: {"temperature": 33.336413834339076,"humidity": 65.31133008367256} Message successfully sent Sending message: {"temperature": 34.92260215374919,"humidity": 69.53101153342156} Message successfully sent確認方法について
AWSとAzureでメッセージが届いたか確認する方法が違ったので、紹介します。
AWSの場合
マネジメントコンソールのテストで確認できます。
Azureの場合
CLIコマンドで確認できます。
az iot hub monitor-events --hub-name iot-hub-00001 --device-id Device001
Starting event monitor, filtering on device: Device001, use ctrl-c to stop... { "event": { "origin": "Device001", "module": "", "interface": "", "component": "", "payload": "{\"temperature\": 24.86829506815134,\"humidity\": 62.82101201700818}" } } { "event": { "origin": "Device001", "module": "", "interface": "", "component": "", "payload": "{\"temperature\": 27.671191300371653,\"humidity\": 70.30860685264159}" } } { "event": { "origin": "Device001", "module": "", "interface": "", "component": "", "payload": "{\"temperature\": 22.581311567865644,\"humidity\": 66.70979111038993}" } } Stopping event monitor...
おしまい。
- 投稿日:2020-08-30T07:19:30+09:00
AWS日記17 (API Gateway)
はじめに
今回は API Gateway の WebSocket を試します。
簡易なチャットページを作成します。
Lambda関数・SAMテンプレート準備
[Amazon API Gatewayの資料]
Amazon API Gateway
Amazon API Gateway とは
Amazon API Gateway の料金AWS SAM テンプレート作成
AWS SAM テンプレートで API-Gateway , Lambda , DynamoDb, S3 の設定をします。
[参考資料]
AWS SAM テンプレートを作成する
template.yml
template.ymlAWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: Serverless Chat Page Parameters: ApplicationName: Type: String Default: 'Serverless Chat Page' ChatWebSocketApiName: Type: String Default: 'ChatWebSocket' ChatFrontApiName: Type: String Default: 'ChatFront' ChatOnConnectFunctionName: Type: String Default: 'ChatOnConnectFunction' ChatOnDisconnectFunctionName: Type: String Default: 'ChatOnDisconnectFunction' ChatOnSendFunctionName: Type: String Default: 'ChatOnSendFunction' ChatCronFunctionName: Type: String Default: 'ChatCronFunction' ChatFrontFunctionName: Type: String Default: 'ChatFrontFunction' ConnectionTableName: Type: String Default: 'chat_connection' MessageTableName: Type: String Default: 'chat_message' LimitConnectionCount: Type: String Default: '10' LimitMessageCount: Type: String Default: '100' Metadata: AWS::ServerlessRepo::Application: Name: Serverless-Application-Simple-Chat Description: 'Serverless Application Simple Chat' Author: tanaka-takurou SpdxLicenseId: MIT LicenseUrl: LICENSE.txt ReadmeUrl: README.md Labels: ['ServerlessRepo'] HomePageUrl: https://github.com/tanaka-takurou/serverless-chat-page-go/ SemanticVersion: 0.0.1 SourceCodeUrl: https://github.com/tanaka-takurou/serverless-chat-page-go/ Resources: ServerlessChatWebSocket: Type: AWS::ApiGatewayV2::Api Properties: Name: !Ref ChatWebSocketApiName ProtocolType: WEBSOCKET RouteSelectionExpression: "$request.body.action" ConnectRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ServerlessChatWebSocket RouteKey: $connect AuthorizationType: NONE OperationName: ConnectRoute Target: !Join - '/' - - 'integrations' - !Ref ConnectInteg ConnectInteg: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ServerlessChatWebSocket Description: Connect Integration IntegrationType: AWS_PROXY IntegrationUri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunction.Arn}/invocations DisconnectRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ServerlessChatWebSocket RouteKey: $disconnect AuthorizationType: NONE OperationName: DisconnectRoute Target: !Join - '/' - - 'integrations' - !Ref DisconnectInteg DisconnectInteg: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ServerlessChatWebSocket Description: Disconnect Integration IntegrationType: AWS_PROXY IntegrationUri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunction.Arn}/invocations SendRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ServerlessChatWebSocket RouteKey: send AuthorizationType: NONE OperationName: SendRoute Target: !Join - '/' - - 'integrations' - !Ref SendInteg SendInteg: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ServerlessChatWebSocket Description: Send Integration IntegrationType: AWS_PROXY IntegrationUri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnSendFunction.Arn}/invocations Deployment: Type: AWS::ApiGatewayV2::Deployment DependsOn: - ConnectRoute - SendRoute - DisconnectRoute Properties: ApiId: !Ref ServerlessChatWebSocket Stage: Type: AWS::ApiGatewayV2::Stage Properties: StageName: Prod Description: Prod Stage DeploymentId: !Ref Deployment ApiId: !Ref ServerlessChatWebSocket ConnectionTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: "connectionId" AttributeType: "S" KeySchema: - AttributeName: "connectionId" KeyType: "HASH" ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 SSESpecification: SSEEnabled: True TableName: !Ref ConnectionTableName MessageTable: Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: "id" AttributeType: "N" KeySchema: - AttributeName: "id" KeyType: "HASH" ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 SSESpecification: SSEEnabled: True TableName: !Ref MessageTableName ImgBucket: Type: AWS::S3::Bucket OnConnectFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Ref ChatOnConnectFunctionName CodeUri: api/connect/bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'Chat OnConnect Function' Environment: Variables: CONNECTION_TABLE_NAME: !Ref ConnectionTableName LIMIT_MESSAGE_COUNT: !Ref LimitMessageCount LIMIT_CONNECTION_COUNT: !Ref LimitConnectionCount REGION: !Ref 'AWS::Region' Policies: - DynamoDBCrudPolicy: TableName: !Ref ConnectionTableName OnConnectPermission: Type: AWS::Lambda::Permission DependsOn: - ServerlessChatWebSocket Properties: Action: lambda:InvokeFunction FunctionName: !Ref OnConnectFunction Principal: apigateway.amazonaws.com OnDisconnectFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Ref ChatOnDisconnectFunctionName CodeUri: api/disconnect/bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'Chat OnDisconnect Function' Environment: Variables: CONNECTION_TABLE_NAME: !Ref ConnectionTableName REGION: !Ref 'AWS::Region' Policies: - DynamoDBCrudPolicy: TableName: !Ref ConnectionTableName OnDisconnectPermission: Type: AWS::Lambda::Permission DependsOn: - ServerlessChatWebSocket Properties: Action: lambda:InvokeFunction FunctionName: !Ref OnDisconnectFunction Principal: apigateway.amazonaws.com OnSendFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Ref ChatOnSendFunctionName CodeUri: api/send/bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'Chat OnSendFunction Function' Environment: Variables: CONNECTION_TABLE_NAME: !Ref ConnectionTableName MESSAGE_TABLE_NAME: !Ref MessageTableName BUCKET_NAME: !Ref ImgBucket LIMIT_MESSAGE_COUNT: !Ref LimitMessageCount LIMIT_CONNECTION_COUNT: !Ref LimitConnectionCount REGION: !Ref 'AWS::Region' Policies: - DynamoDBCrudPolicy: TableName: !Ref ConnectionTableName - DynamoDBCrudPolicy: TableName: !Ref MessageTableName - S3CrudPolicy: BucketName: !Ref ImgBucket - Statement: - Effect: Allow Action: - 'execute-api:ManageConnections' Resource: - !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessChatWebSocket}/*' SendMessagePermission: Type: AWS::Lambda::Permission DependsOn: - ServerlessChatWebSocket Properties: Action: lambda:InvokeFunction FunctionName: !Ref OnSendFunction Principal: apigateway.amazonaws.com ServerlessChatFrontPage: Type: AWS::Serverless::HttpApi FrontPageFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Ref ChatFrontFunctionName CodeUri: bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'Chat Front Function' Events: testapi: Type: HttpApi Properties: Path: '/' Method: get ApiId: !Ref ServerlessChatFrontPage Environment: Variables: BUCKET_NAME: !Ref ImgBucket MESSAGE_TABLE_NAME: !Ref MessageTableName LIMIT_MESSAGE_COUNT: !Ref LimitMessageCount WEBSOCKET_URL: !Join [ '', [ 'wss://', !Ref ServerlessChatWebSocket, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/Prod'] ] REGION: !Ref 'AWS::Region' Policies: - DynamoDBCrudPolicy: TableName: !Ref MessageTableName ChatApiPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref FrontPageFunction Principal: apigateway.amazonaws.com CronFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Ref ChatCronFunctionName CodeUri: api/cron/bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'Chat Cron Function' Environment: Variables: CONNECTION_TABLE_NAME: !Ref ConnectionTableName REGION: !Ref 'AWS::Region' STACK_NAME: !Ref 'AWS::StackName' Policies: - DynamoDBCrudPolicy: TableName: !Ref ConnectionTableName ScheduledRule: Type: AWS::Events::Rule Properties: Description: ScheduledRule ScheduleExpression: 'rate(24 hours)' State: 'ENABLED' Targets: - Arn: !GetAtt CronFunction.Arn Id: TargetCronFunction CronFunctionPermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref CronFunction Action: lambda:InvokeFunction Principal: 'events.amazonaws.com' SourceArn: !GetAtt ScheduledRule.Arn Outputs: WebSocketURI: Description: "The WSS Protocol URI to connect to" Value: !Join [ '', [ 'wss://', !Ref ServerlessChatWebSocket, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/Prod'] ] FrontPageURI: Description: "The Front Page URI to connect to" Value: !Join [ '', [ 'https://', !Ref ServerlessChatFrontPage, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/'] ]WebSocket用のAPI-Gatewayの設定
ServerlessChatWebSocket: Type: AWS::ApiGatewayV2::Api Properties: Name: !Ref ChatWebSocketApiName ProtocolType: WEBSOCKET RouteSelectionExpression: "$request.body.action"WebSocket接続用のルートの設定
ConnectRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ServerlessChatWebSocket RouteKey: $connect AuthorizationType: NONE OperationName: ConnectRoute Target: !Join - '/' - - 'integrations' - !Ref ConnectInteg ConnectInteg: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ServerlessChatWebSocket Description: Connect Integration IntegrationType: AWS_PROXY IntegrationUri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnConnectFunction.Arn}/invocationsWebSocket接続先にデータを送る用のルートの設定
SendRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ServerlessChatWebSocket RouteKey: send AuthorizationType: NONE OperationName: SendRoute Target: !Join - '/' - - 'integrations' - !Ref SendInteg SendInteg: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ServerlessChatWebSocket Description: Send Integration IntegrationType: AWS_PROXY IntegrationUri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnSendFunction.Arn}/invocationsWebSocket切断用のルートの設定
DisconnectRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ServerlessChatWebSocket RouteKey: $disconnect AuthorizationType: NONE OperationName: DisconnectRoute Target: !Join - '/' - - 'integrations' - !Ref DisconnectInteg DisconnectInteg: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ServerlessChatWebSocket Description: Disconnect Integration IntegrationType: AWS_PROXY IntegrationUri: Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${OnDisconnectFunction.Arn}/invocationsLambda関数作成
※ Lambda関数は aws-lambda-go を利用し、apigatewayの周りの処理は aws-sdk-go-v2 を利用しました。
WebsocketのコネクションIDを取得するには APIGatewayWebsocketProxyRequest.RequestContext.ConnectionID を使う
func HandleRequest(ctx context.Context, request events.APIGatewayWebsocketProxyRequest) (Response, error) { ... if err == nil && int(*connectionCount) < limitCount { err = putConnection(ctx, request.RequestContext.ConnectionID) } else if int(*connectionCount) >= limitCount { err = errors.New("too many connections") } ... }WebSocket接続先にデータを送るには PostToConnectionRequest を使う
connectionRequest := apigatewayClient.PostToConnectionRequest(&apigatewaymanagementapi.PostToConnectionInput{ Data: jsonBytes, ConnectionId: &connectionId, }) _, err := connectionRequest.Send(ctx)終わりに
これまでAPI Gatewayは、ほぼREST APIのみ利用してきましたが、用途に合わせて HTTP API や WebSocket API も使い分けていこうと思います。
- 投稿日:2020-08-30T06:29:06+09:00
AWS日記16 (Serverless Application Repository)
はじめに
今回は AWS Serverless Application Repository を試します。
サーバーレスアプリケーションを管理するページを作成します。
Lambda関数・SAMテンプレート準備
Serverless Application Repositoryの「マイアプリケーション」にアプリケーションが表示されている状態にします。
[AWS Serverless Application Repositoryの資料]
AWS Serverless Application Repository
よくある質問と規約
リポジトリへのアプリケーションの公開AWS SAM テンプレート作成
AWS SAM テンプレートで API-Gateway , Lambdaの設定をします。
[参考資料]
AWS SAM テンプレートを作成する
template.yml
template.ymlAWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: Serverless Application Management Page Parameters: ApplicationName: Type: String Default: 'ServerlessApplicationManagementPage' FrontPageApiStageName: Type: String Default: 'ProdStage' Resources: FrontPageApi: Type: AWS::Serverless::Api Properties: Name: ServerlessApplicationManagementPageApi EndpointConfiguration: REGIONAL StageName: !Ref FrontPageApiStageName FrontPageFunction: Type: AWS::Serverless::Function Properties: FunctionName: ServerlessApplicationManagementPageFrontFunction CodeUri: bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'ApplicationManagement Function' Policies: Environment: Variables: REGION: !Ref 'AWS::Region' API_PATH: !Join [ '', [ '/', !Ref FrontPageApiStageName, '/api'] ] Events: FrontPageApi: Type: Api Properties: Path: '/' Method: get RestApiId: !Ref FrontPageApi MainFunction: Type: AWS::Serverless::Function Properties: FunctionName: ServerlessApplicationManagementPageApiFunction CodeUri: api/bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'ApplicationManagement Function' Role: !GetAtt MainFunctionRole.Arn Environment: Variables: REGION: !Ref 'AWS::Region' Events: FrontPageApi: Type: Api Properties: Path: '/api' Method: post RestApiId: !Ref FrontPageApi MainFunctionRole: Type: AWS::IAM::Role Properties: MaxSessionDuration: 3600 AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: Service: - 'lambda.amazonaws.com' Action: - 'sts:AssumeRole' Policies: - PolicyName: ManagementApplicationPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Action: - 'serverlessrepo:ListApplications' - 'serverlessrepo:CreateCloudFormationTemplate' Resource: '*' - Effect: 'Allow' Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: '*' - Effect: 'Allow' Action: - 'cloudformation:DescribeStackResources' - 'cloudformation:DeleteStack' - 'cloudformation:CreateStack' - 'cloudformation:ListStacks' - 'cloudformation:ListStackResources' - 'cloudformation:CreateChangeSet' Resource: '*' - Effect: 'Allow' Action: - 'lambda:*' - 'events:RemoveTargets' - 'events:PutTargets' - 'events:DescribeRule' - 'events:DeleteRule' - 'events:PutRule' - 'iam:DeleteRolePolicy' - 'iam:DeleteRole' - 'iam:CreateRole' - 'iam:AttachRolePolicy' - 'iam:PutRolePolicy' - 'iam:GetRole' - 'iam:PassRole' Resource: '*' - Effect: 'Allow' Action: - 's3:PutObject' - 's3:GetObject' Resource: '*' - Effect: 'Allow' Action: - 'apigateway:*' Resource: '*' Outputs: APIURI: Description: "URI" Value: !Join [ '', [ 'https://', !Ref FrontPageApi, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/',!Ref FrontPageApiStageName,'/'] ]API-Gatewayの設定
FrontPageApi: Type: AWS::Serverless::Api Properties: Name: ServerlessApplicationManagementPageApi EndpointConfiguration: REGIONAL StageName: !Ref FrontPageApiStageNameフロントエンド用Lambdaの設定
FrontPageFunction: Type: AWS::Serverless::Function Properties: FunctionName: ServerlessApplicationManagementPageFrontFunction CodeUri: bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'ApplicationManagement Function' Policies: Environment: Variables: REGION: !Ref 'AWS::Region' API_PATH: !Join [ '', [ '/', !Ref FrontPageApiStageName, '/api'] ] Events: FrontPageApi: Type: Api Properties: Path: '/' Method: get RestApiId: !Ref FrontPageApiバックエンド用Lambdaの設定
MainFunction: Type: AWS::Serverless::Function Properties: FunctionName: ServerlessApplicationManagementPageApiFunction CodeUri: api/bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'ApplicationManagement Function' Role: !GetAtt MainFunctionRole.Arn Environment: Variables: REGION: !Ref 'AWS::Region' Events: FrontPageApi: Type: Api Properties: Path: '/api' Method: post RestApiId: !Ref FrontPageApiLambda関数作成
main.go
main.gopackage main import ( "os" "fmt" "log" "time" "context" "strings" "net/http" "encoding/json" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/external" "github.com/aws/aws-sdk-go-v2/service/cloudformation" "github.com/aws/aws-sdk-go-v2/service/serverlessapplicationrepository" ) type Application struct { Name string `json:"name"` Description string `json:"description"` Stack Stack `json:"stack"` } type Stack struct { Name string `json:"name"` Status string `json:"status"` Url string `json:"url"` } type APIResponse struct { Message string `json:"message"` ApplicationList []Application `json:"applicationList"` } type Response events.APIGatewayProxyResponse var cfg aws.Config var cloudformationClient *cloudformation.Client var serverlessApplicationRepositoryClient *serverlessapplicationrepository.Client const layout string = "20060102150405.000" func HandleRequest(ctx context.Context, request events.APIGatewayProxyRequest) (Response, error) { var jsonBytes []byte var err error d := make(map[string]string) json.Unmarshal([]byte(request.Body), &d) if v, ok := d["action"]; ok { switch v { case "status" : l, e := getApplications(ctx) if e != nil { err = e } else { jsonBytes, _ = json.Marshal(APIResponse{Message: "Success.", ApplicationList: l}) } case "create" : if n, ok := d["name"]; ok { e := createStack(ctx, n) if e != nil { err = e } else { jsonBytes, _ = json.Marshal(APIResponse{Message: "Success.", ApplicationList: nil}) } } case "delete" : if n, ok := d["name"]; ok { e := deleteStack(ctx, n) if e != nil { err = e } else { jsonBytes, _ = json.Marshal(APIResponse{Message: "Success.", ApplicationList: nil}) } } } } log.Print(request.RequestContext.Identity.SourceIP) if err != nil { log.Print(err) jsonBytes, _ = json.Marshal(APIResponse{Message: fmt.Sprint(err)}) return Response{ StatusCode: http.StatusInternalServerError, Body: string(jsonBytes), }, nil } return Response { StatusCode: http.StatusOK, Body: string(jsonBytes), }, nil } func getApplications(ctx context.Context)([]Application, error) { if serverlessApplicationRepositoryClient == nil { serverlessApplicationRepositoryClient = serverlessapplicationrepository.New(cfg) } req := serverlessApplicationRepositoryClient.ListApplicationsRequest(&serverlessapplicationrepository.ListApplicationsInput{}) res, err := req.Send(ctx) if err != nil { return nil, err } var applicationList []Application for _, i := range res.ListApplicationsOutput.Applications { applicationList = append(applicationList, Application{ Name: aws.StringValue(i.Name), Description: aws.StringValue(i.Description), Stack: Stack{}, }) } applicationList, err = addStackData(ctx, applicationList) if err != nil { return nil, err } return applicationList, nil } func getApplicationId(ctx context.Context, name string)(string, error) { if serverlessApplicationRepositoryClient == nil { serverlessApplicationRepositoryClient = serverlessapplicationrepository.New(cfg) } req := serverlessApplicationRepositoryClient.ListApplicationsRequest(&serverlessapplicationrepository.ListApplicationsInput{}) res, err := req.Send(ctx) if err != nil { return "", err } var applicationId string for _, i := range res.ListApplicationsOutput.Applications { if name == aws.StringValue(i.Name) { applicationId = aws.StringValue(i.ApplicationId) break } } return applicationId, nil } func getTemplateUrl(ctx context.Context, applicationId string)(string, error) { if serverlessApplicationRepositoryClient == nil { serverlessApplicationRepositoryClient = serverlessapplicationrepository.New(cfg) } req := serverlessApplicationRepositoryClient.CreateCloudFormationTemplateRequest(&serverlessapplicationrepository.CreateCloudFormationTemplateInput{ ApplicationId: aws.String(applicationId), }) res, err := req.Send(ctx) if err != nil { return "", err } return aws.StringValue(res.CreateCloudFormationTemplateOutput.TemplateUrl), nil } func addStackData(ctx context.Context, applicationList []Application)([]Application, error) { if cloudformationClient == nil { cloudformationClient = cloudformation.New(cfg) } req := cloudformationClient.ListStacksRequest(&cloudformation.ListStacksInput{ StackStatusFilter: []cloudformation.StackStatus{ cloudformation.StackStatusCreateComplete, cloudformation.StackStatusCreateInProgress, cloudformation.StackStatusDeleteInProgress, }, }) res, err := req.Send(ctx) if err != nil { return nil, err } for _, i := range res.ListStacksOutput.StackSummaries { stackName := aws.StringValue(i.StackName) for n, j := range applicationList { if strings.HasPrefix(stackName, j.Name) { var url string if i.StackStatus == cloudformation.StackStatusCreateComplete { req_ := cloudformationClient.ListStackResourcesRequest(&cloudformation.ListStackResourcesInput{StackName: i.StackName}) res_, err := req_.Send(ctx) if err != nil { log.Println(err) break } for _, j := range res_.ListStackResourcesOutput.StackResourceSummaries { if aws.StringValue(j.ResourceType) == "AWS::ApiGatewayV2::Api" { url = "https://" + aws.StringValue(j.PhysicalResourceId) + ".execute-api." + os.Getenv("REGION") + ".amazonaws.com/" } } } applicationList[n].Stack = Stack{Name: stackName, Status: string(i.StackStatus), Url: url} break } } } return applicationList, nil } func createStack(ctx context.Context, name string) error { applicationId, err := getApplicationId(ctx, name) if err != nil { log.Println(err) return err } templateUrl, err := getTemplateUrl(ctx, applicationId) if err != nil { log.Println(err) return err } t := time.Now() stackName := name + strings.Replace(t.Format(layout), ".", "", 1) if cloudformationClient == nil { cloudformationClient = cloudformation.New(cfg) } req := cloudformationClient.CreateStackRequest(&cloudformation.CreateStackInput{ Capabilities: []cloudformation.Capability{ cloudformation.CapabilityCapabilityIam, cloudformation.CapabilityCapabilityAutoExpand, }, StackName: aws.String(stackName), TemplateURL: aws.String(templateUrl), }) _, err = req.Send(ctx) if err != nil { log.Println(err) return err } return nil } func deleteStack(ctx context.Context, name string) error { if cloudformationClient == nil { cloudformationClient = cloudformation.New(cfg) } req := cloudformationClient.DeleteStackRequest(&cloudformation.DeleteStackInput{ StackName: aws.String(name), }) _, err := req.Send(ctx) if err != nil { log.Println(err) } return nil } func getTargetStack(name string, list []Stack) Stack { var stack Stack for _, i := range list { if i.Name == name { stack = i break } } return stack } func init() { var err error cfg, err = external.LoadDefaultAWSConfig() cfg.Region = os.Getenv("REGION") if err != nil { log.Print(err) } } func main() { lambda.Start(HandleRequest) }アプリケーション一覧を取得するには ListApplicationsRequest を使う
req := serverlessApplicationRepositoryClient.ListApplicationsRequest(&serverlessapplicationrepository.ListApplicationsInput{}) res, err := req.Send(ctx)※ Serverless Application Repositoryのマイアプリケーションに表示されているアプリケーション一覧が取得できます。
CloudFormationテンプレートを作成するには CreateCloudFormationTemplateRequest を使う
req := serverlessApplicationRepositoryClient.CreateCloudFormationTemplateRequest(&serverlessapplicationrepository.CreateCloudFormationTemplateInput{ ApplicationId: aws.String(applicationId), }) res, err := req.Send(ctx)スタックを作成するには CreateStackRequest を使う
req := cloudformationClient.CreateStackRequest(&cloudformation.CreateStackInput{ Capabilities: []cloudformation.Capability{ cloudformation.CapabilityCapabilityIam, cloudformation.CapabilityCapabilityAutoExpand, }, StackName: aws.String(stackName), TemplateURL: aws.String(templateUrl), }) _, err = req.Send(ctx)スタック一覧を取得するには ListStacksRequest を使う
req := cloudformationClient.ListStacksRequest(&cloudformation.ListStacksInput{ StackStatusFilter: []cloudformation.StackStatus{ cloudformation.StackStatusCreateComplete, cloudformation.StackStatusCreateInProgress, cloudformation.StackStatusDeleteInProgress, }, }) res, err := req.Send(ctx)上記の例では、フィルターを設定し [作成完了、作成中、削除中]のスタックのみを取得しています。
スタックを削除するには DeleteStackRequest を使う
req := cloudformationClient.DeleteStackRequest(&cloudformation.DeleteStackInput{ StackName: aws.String(name), }) _, err := req.Send(ctx)終わりに
Serverless Application Repositoryにマイアプリケーションを発行することで、Web環境のみの状況でもサーバレスアプリケーションを作成することができます。
AWS-SAMとあわせて使い慣れていこうと思います。
- 投稿日:2020-08-30T05:35:29+09:00
AWS日記15 (Serverless Application Model)
はじめに
今回は AWS Serverless Application Model (AWS SAM)を試します。
アクセスすると自己削除するWebページを作成します。
Lambda関数・SAMテンプレート準備
AWS SAM CLI をインストールします
S3の準備をします[AWS SAMの資料]
AWS サーバーレスアプリケーションモデル
AWS Serverless Application ModelAWS SAM テンプレート作成
AWS SAM テンプレートで API-Gateway , Lambdaの設定をします。
[参考資料]
AWS SAM テンプレートを作成する
template.yml
template.ymlAWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Serverless Application Ephemerality Parameters: ApplicationName: Type: String Default: 'ServerlessApplicationEphemeralityMinimum' EphemeralityFunctionName: Type: String Default: 'EphemeralityFunctionMinimum' Metadata: AWS::ServerlessRepo::Application: Name: Serverless-Application-Minimum Description: 'This application is deleted when accessed.' Author: tanaka-takurou SpdxLicenseId: MIT LicenseUrl: LICENSE.txt ReadmeUrl: README.md Labels: ['ServerlessRepo'] HomePageUrl: https://github.com/tanaka-takurou/serverless-application-ephemerality-page-go/tree/minimum SemanticVersion: 0.0.2 SourceCodeUrl: https://github.com/tanaka-takurou/serverless-application-ephemerality-page-go/tree/minimum Resources: EphemeralityApi: Type: AWS::Serverless::HttpApi EphemeralityFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Ref EphemeralityFunctionName CodeUri: bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'Front Function' Role: !GetAtt EphemeralityFunctionRole.Arn Events: EphemeralityApi: Type: HttpApi Properties: Path: '/' Method: get ApiId: !Ref EphemeralityApi Environment: Variables: COUNT: "0" LIMIT: "0" REGION: !Ref AWS::Region STACK_NAME: !Ref AWS::StackName FUNCTION_NAME: !Ref EphemeralityFunctionName EphemeralityApiPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref EphemeralityFunction Principal: apigateway.amazonaws.com EphemeralityFunctionRole: Type: AWS::IAM::Role Properties: MaxSessionDuration: 3600 AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Principal: Service: - 'lambda.amazonaws.com' Action: - 'sts:AssumeRole' Policies: - PolicyName: KillFunctionPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: 'Allow' Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:PutLogEvents' Resource: '*' - Effect: 'Allow' Action: - 'cloudformation:DescribeStackResources' - 'cloudformation:DeleteStack' Resource: '*' - Effect: 'Allow' Action: - 'lambda:*' - 'events:RemoveTargets' - 'events:DeleteRule' - 'iam:DeleteRolePolicy' - 'iam:DeleteRole' Resource: '*' - Effect: 'Allow' Action: - 'apigateway:*' Resource: '*' Outputs: APIURI: Value: !Join [ '', [ 'https://', !Ref EphemeralityApi, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/'] ]API-Gatewayの設定は以下の部分
EphemeralityApi: Type: AWS::Serverless::HttpApiLambdaの設定は以下の部分
EphemeralityFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Ref EphemeralityFunctionName CodeUri: bin/ Handler: main MemorySize: 256 Runtime: go1.x Description: 'Front Function' Role: !GetAtt EphemeralityFunctionRole.Arn Events: EphemeralityApi: Type: HttpApi Properties: Path: '/' Method: get ApiId: !Ref EphemeralityApi Environment: Variables: COUNT: "0" LIMIT: "0" REGION: !Ref AWS::Region STACK_NAME: !Ref AWS::StackName FUNCTION_NAME: !Ref EphemeralityFunctionNameLambda関数作成
main.go
main.gopackage main import ( "os" "log" "context" "strconv" "net/http" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/external" slambda "github.com/aws/aws-sdk-go-v2/service/lambda" "github.com/aws/aws-sdk-go-v2/service/cloudformation" ) var cfg aws.Config func HandleRequest(ctx context.Context, request events.APIGatewayV2HTTPRequest) (events.APIGatewayProxyResponse, error) { count, _ := strconv.Atoi(os.Getenv("COUNT")) limit, _ := strconv.Atoi(os.Getenv("LIMIT")) if count < limit { client := slambda.New(cfg) req := client.GetFunctionConfigurationRequest(&slambda.GetFunctionConfigurationInput{ FunctionName: aws.String(os.Getenv("FUNCTION_NAME")), }) res, err := req.Send(ctx) if err != nil { log.Println(err) } else { env := res.GetFunctionConfigurationOutput.Environment.Variables env["COUNT"] = strconv.Itoa(count + 1) req_ := client.UpdateFunctionConfigurationRequest(&slambda.UpdateFunctionConfigurationInput{ FunctionName: aws.String(os.Getenv("FUNCTION_NAME")), Environment: &slambda.Environment{ Variables: env, }, }) _, err := req_.Send(ctx) if err != nil { log.Println(err) } } } else { client := cloudformation.New(cfg) req := client.DeleteStackRequest(&cloudformation.DeleteStackInput{ StackName: aws.String(os.Getenv("STACK_NAME")), }) _, err := req.Send(ctx) if err != nil { log.Println(err) } } return events.APIGatewayProxyResponse{ StatusCode: http.StatusOK, IsBase64Encoded: false, Body: "<html><head><title>Serverless Application Ephemerality</title></head><body><span>Serverless Application Ephemerality</span></body></html>", Headers: map[string]string{ "Content-Type": "text/html", }, }, nil } func init() { var err error cfg, err = external.LoadDefaultAWSConfig() cfg.Region = os.Getenv("REGION") if err != nil { log.Print(err) } } func main() { lambda.Start(HandleRequest) }スタックを削除するには DeleteStackRequest を使う
client := cloudformation.New(cfg) req := client.DeleteStackRequest(&cloudformation.DeleteStackInput{ StackName: aws.String(os.Getenv("STACK_NAME")), }) _, err := req.Send(ctx)※ この処理により 作成したWebページが削除されます。
デプロイ
sam package --output-template-file packaged.yml --s3-bucket "${bucket}" sam deploy --stack-name "${stack}" --capabilities CAPABILITY_IAM --template-file packaged.yml※ ${bucket} には 「準備」 で作成した S3バケット名を入力
※ ${stack} には スタック名を入力 (既にあるスタックを指定すると、スタックを更新します)・デプロイが完了すると、作成したWebページにアクセスできるようになります。
・デプロイ完了後 CloudFormationのスタック一覧 に表示されます。
パブリッシュ
sam package --output-template-file packaged.yml --s3-bucket "${bucket}" sam publish --template packaged.yml" --region "${AWS_DEFAULT_REGION}"※ ${bucket} には 「準備」 で作成した S3バケット名を入力
※ ${AWS_DEFAULT_REGION} には リージョン (ap-northeast-1 など) を入力・パブリッシュ完了後 Serverless Application Repositoryのマイアプリケーション一覧 に表示されます。
削除
aws cloudformation delete-stack --stack-name "${stack}"※ ${stack} には スタック名を入力
終わりに
Policy周りが原因でデプロイする際にエラーが発生することが多くありました。
使い慣れることで、AWS管理画面よりも簡単に、サーバレスアプリケーションの作成・削除・複製できそうです。
参考資料
serverless-application-model
究極のCloudFormationをたずねて三千里
AWS SAMを使ってみる
AWS SAMのコマンドをまとめてみた
AWS SAM アプリケーションをデプロイする
今日から始めるサーバーレス SAM【API Gateway + Lambda + DynamoDB】
SAMで作成されるApiGatewayをエッジ最適化以外で作成したいとき
aws-sam-cliでLambda,DynamoDBのサーバーレスアプリケーション開発に入門してみる
AWS SAM で Hello World する
ゼロから始める AWS SAM 入門
SAM で API Gateway の CloudWatch Logs を有効にしたい
CloudFormationでクソが!って叫んだこと
SAMでtemplate.yamlの記述方法(Events、Policyはここを参照して書く)
AWS SAM CLI 再入門 2020.07
AWS SAMでローカル環境でS3とDynamoDBを扱うLambdaを実行する
cloudformation/samでsns通知をslackに流すgoのlambdaを作る
CloudFormation で Cognito
AWS SAMのテンプレートではリソースごとに!Refと!GetAttの戻り値が違う
CloudFormationスタック作成エラー: Requires capabilities : [CAPABILITY_NAMED_IAM]
AWS SAMを利用してGolangなLambdaをデプロイする
多分わかりやすいCloudFormation入門とチュートリアル
CloudFormationでAWS Lambdaを作成・更新する際のベストプラクティス
CloudFormationでAPI Gateway+LambdaなAPIを作成する
AWS SAMを使う前にCloudFormationテンプレートを書こう
- 投稿日:2020-08-30T02:37:15+09:00
RDSはデフォルトパラメータグループはやめよう
RDSのMySQLをデフォルトパラメータグループで運用していたのですが、カスタムパラメータグループにしておけばよかったと後悔した話です。
RDSのInnoDBモニターを有効化したかった。
DeadLockなどのログが見たくてInnoDBモニターを有効化しようと思いました。
有効化にはMySQLのDBパラメータinnodb_status_output/innodb_status_output_locksをONにする必要があります。
ですが、デフォルトパラメータグループのパラメータは変えることが出来ません。
そのためカスタムパラメータグループを作成し付け替えたのですが、パラメータグループの付け替えには必ずインスタンスを再起動する必要がありました。ということでRDSは作成時からカスタムパラメータグループで運用するよう心掛けましょう。
- 投稿日:2020-08-30T01:09:44+09:00
#1 Djangoのwebアプリケーションをデプロイするまで(AWSのEC2でインスタンス構築編)
はじめに
私はプログラミング言語の初学者です。
その為、読み間違いや理解不足の部分があると思います。
その場合はコメントで教えて頂けると幸いです。「ゴール」は事前に作成したPythonのwebアプリケーションをデプロイする所まで行きたいと思います。出来るだけ初学者向けに書くつもりです。
作成手順
- AWSのEC2でインスタンスを構築後に、SSH通信をする ←現在はここです。
- 構築したインスタンスの中にPythonの環境を構築する
- インスタンスの中に仮想環境を構築し、Django等をインストール(フレームワーク)
- PostgreSQLを設定する(データベース)
- インターネット上にアプリケーションが接続出来るように設定する(デプロイ前半)
- NginxとGunicornの設定する(デプロイ後半)
インスタンスの作成の仕方
ステップ1(インスタンスの作成)
AWSのEC2を選択し、この画面に移動します。
画面右上の矢印がある所をクリックします。(ここはリージョンというものを選択する所です。)
*特にこだわりが無ければどこでもいいですが、EC2を使用する前はここを必ずチェックしましょう。
「インスタンスの作成」をクリックします。
ステップ2(Amazon マシンイメージの選択)
「インスタンスを作成」をクリックすると、この画面に移動すると思います。
ここで仮想サーバを決めます。
今回はUbuntu Server 18.04 LTS (HVM), SSD Volume Type - ami-0bcc094591f354be2 (64 ビット x86) / ami-0bc556e0c71e1b467 (64 ビット Arm)を使います。
ステップ3(インスタンスタイプを選択)
インスタンスタイプは「 t2.micro 無料利用枠の対象 」を選択
*ここで「無料利用枠の対象」の物を選択しないと、お金が掛かります。
ステップ4(ストレージの設定)
「ステップ7:インスタンス作成の確認」の所まで何も考えずに「次へ」ボタンをクリックします。
*必要なことがあれば、作成した後でも変更ができると思います。
ステップ5(キーペアの作成+ダウンロード)
「ステップ7:インスタンス作成の確認」のページの「起動」をクリックします。
すると、この画面になると思いますので「新しいキーペアの作成」を選択します。
「キーペア名」はご自身の好きな名前にしてください。
*ここでは[test]にしておきます。
そしてダウンロードします。
*このインスタンスの使用中は必ず削除しないでください。また誰にも渡らないようにしてください。
ちなみにダウンロードがされると、filderの中にダウンロードがされると思いますのでDesktopに移動しておいてください。後で使います。
ステップ6(SSH通信をしてみる)
キーペアのダウンロードされているか確認してください。
その後にDesktopの中に移動してあることが前提で進めます。
キーペアはXXX.pemというファイルでダウンロードされていると思いますが、このファイルをchmodコマンドでパーミッションを変更しないとSSH接続できません。
- 'hostname'ですが、インスタンスの説明の中にあるパブリック DNS: xxx.amazonaws.comです。
- XXX.pemの'XXX'はステップ5で決めた「キーペア名」になります。
- ubuntuはデフォルトの名前になります。
パーミッションは下記のコードを打ちます。 $ chmod 400 XXX.pem SSH接続できるか確認します。 $ ssh -i XXX.pem 'user name'@'hostname' この記事ではこうなります。 $ ssh -i test.pem ubuntu@ec2.00-000-000-00.compute-1.amazonaws.comターミナルで(yes/no)聞かれるので、yesと打ちます。
そうすると
ubuntu@ip-000-00-00-000:~$ というプロンプトになるはずです。続きは次の記事に書きます。