- 投稿日:2021-01-15T18:37:04+09:00
CodePipelineのbuildのレビューを一気に承認したい
- 投稿日:2021-01-15T16:30:37+09:00
FlutterでAmplify Datastoreを使ってみる
お客様からの強いご要望でバックエンドはAWSの利用必須。Flutter使いたいのにライブラリが用意されていないので泣く泣くReact Nativeで実装するしかない...なんてことありますよね。
そんな方に朗報です。dev版ですが、appsync用のflutterライブラリがついに出ました!
ということで使ってみました。https://docs.amplify.aws/lib/datastore/getting-started/q/platform/flutter
公式の手順に従って実装してみましたがいくつか記述に不足があるようなのでメモを残しておきます。
作成したのは、いつものカウンターアプリのボタンを押すとDynamodbにデータが登録されるシンプルなやつです。
プロジェクト作成
ライブラリ導入
pubspec.yamlファイルに以下の記述を追記します。
公式にはamplify_coreは記載がありませんが、必要なようです。pubspec.yamldependencies: flutter: sdk: flutter amplify_datastore: any amplify_core: anyAmplify CLI
Amplifyの初期化とバックエンドの追加をします。
なお、amplifyのflutter対応は比較的最近導入された機能なので古いバージョンを使っている方は更新しましょう。私は以下のバージョンを利用しました。> amplify -v 4.41.2作成したFlutterプロジェクト直下に移動し以下コマンドを実行します。
"Choose the type of app that you're building"でflutterを選ぶのがポイント。> amplify init ? Enter a name for the project flutterapp ? Enter a name for the environment dev ? Choose your default editor: Visual Studio Code ? Choose the type of app that you're building flutter Please tell us about your project ⚠️ Flutter project support in the Amplify CLI is in DEVELOPER PREVIEW. Only the following categories are supported: * Auth * Analytics (Amazon Pinpoint only) * API (GraphQL only) * Storage ? Where do you want to store your configuration file? ./lib/ ? Do you want to use an AWS profile? Yesバックエンドを追加します。
今回はライブラリを触ってみるだけを目的としているのでサンプルのスキーマ(Todo)をそのまま利用しました。> amplify add api ? Please select from one of the below mentioned services: GraphQL ? Provide API name: flutterapp ? Choose the default authorization type for the API API key ? Enter a description for the API key: ? After how many days from now the API key should expire (1-365): 7 ? Do you want to configure advanced settings for the GraphQL API Yes, I want to make some additional changes. ? Configure additional auth types? No ? Configure conflict detection? Yes ? Select the default resolution strategy Auto Merge ? Do you have an annotated GraphQL schema? No ? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description) ? Do you want to edit the schema now? No> amplify push ✔ Successfully pulled backend environment dev from the cloud. Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | ------------- | --------- | ----------------- | | Api | flutterapp | Create | awscloudformation | ? Are you sure you want to continue? Yes> amplify codegen models処理が完了すると、lib/models配下にModelProvider.dartとTodo.dartというファイルが作成されていることが確認できます。
Amplify Datastoreの導入
公式のサンプルがinitStateで初期化しているのでそれを参考に、main.dartにAmplifyDataStoreを導入します。
(StatefulWidgetクラスはinitStateメソッド持っていないはずなのでサンプルはおそらく誤り?)main.dartimport 'package:flutter/material.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_datastore/amplify_datastore.dart'; import 'package:flutter_app/amplifyconfiguration.dart'; import 'package:flutter_app/models/ModelProvider.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; Amplify amplifyInstance = Amplify(); @override void initState() { super.initState(); AmplifyDataStore datastorePlugin = AmplifyDataStore(modelProvider: ModelProvider.instance); amplifyInstance.addPlugin(dataStorePlugins: [datastorePlugin]); amplifyInstance.configure(amplifyconfig); } void _incrementCounter() async { Todo newTodo = Todo(name: 'HOGE'); await Amplify.DataStore.save(newTodo); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }動作確認
シミュレーターを起動します。
が、以下のようなエラーがでてしまいます...。Launching lib/main.dart on sdk gphone x86 in debug mode... /xxx/flutter_app/android/app/src/debug/AndroidManifest.xml Error: uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [:amplify_core] /xxx/flutter_app/build/amplify_core/intermediates/library_manifest/debug/AndroidManifest.xml as the library might be using APIs not available in 16 Suggestion: use a compatible library with a minSdk of at most 16, or increase this project's minSdk version to at least 21, or use tools:overrideLibrary="com.amazonaws.amplify.amplify_core" to force usage (may lead to runtime failures) FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:processDebugMainManifest'. > Manifest merger failed with multiple errors, see logs * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 10s Running Gradle task 'assembleDebug'... Running Gradle task 'assembleDebug'... Done 13.0s Exception: Gradle task assembleDebug failed with exit code 1どうやら、sdk versionは21以上が必要なようですね。
android/app/build.gradleのminSdkVersionを21に変更します。defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.myapp.flutter_app" minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName }再度ビルドすると無事アプリが起動しました。
右下のボタンをクリックし、Dynamodbを確認するとデータが登録されていることが確認できました。
公式の手順だけだと足りないので、もう少し記載があるとうれしいな。
- 投稿日:2021-01-15T15:25:04+09:00
AWSのlamdaでfirebaseを使う時に沼った話
ハッカソンに出場するためのプロダクトを開発している時に、大沼にハマったので、記事にしてみようと思います。
半年ほど前の記憶を辿りながらですので、ところどころ抜けてるかもしれません。
また、awsのアカウント作成や、aws cliのインストールなど、細かい部分に関しては、記載しませんので、ご了承ください。環境
・MacOS Catalina
$ sw_vers ProductName: macOS ProductVersion: 11.1 BuildVersion: 20C69やろうとしていたこと
iosのカメラで表情を認識した時に、写真を撮影し、AWSのS3にアップロード。アップロードが完了したことをトリガーに、lmada関数が走り出し、AWS Recognitionという表情認識APIにアップロードされた画像を渡す。そして、APIから帰ってきた表情の数値をfirebaseに保存する。保存された情報をブラウザが取得し、グラフとして表示するというプロダクトを作ろうとしていた。
※今回は、私自信がs3に画像がアップロードされてからfirebaseに保存するまでを担当したので、その部分のみの記述になります。
手順
①AWS confing の設定
はじめてのAWSだったので、最初のこの部分の設定がなかなか鬼門でした。
ただ、一度理解すればここはなんなくいけます。$ aws configure --profile user1 AWS Access Key ID [None]: {アクセスキー(各自)} AWS Secret Access Key [None]: {シークレットアクセスキー(各自)} Default region name [None]: ap-northeast-1 Default output format [None]: json
②lamda関数の記述 ※ソースコードは割愛させていただきます。
ここでまさかの大沼ポイントがありました。APIを叩いて、表情を数値化したデータは取れているのですが、firebase admin SDKがうまく反映されておらず、データを保存することができていませんでした。数時間にも及ぶ死闘。(数時間で死闘とかゆうなってゆうのは、やめてください。こちらも理解した上で書いております。)その末に判明したのは、原因がlamda関数において、pythonランタイム(lamda関数をpythonで記述していたため)しか設定していなかった。しかし、firebase admin SDKは、一部C言語でのコードも存在するため、ランタイムエラーが起こり、うまく走っていなかったのです。
最終的には、lamda関数にカスタムランタイムでC言語を設定することで解決することができました。
ここまでくれば、あとはブラウザからfirestoreに保存されているデータを取得するだけなので、簡単です。
と思いきや、ここからもAPI関係で沼にハマったらしく(担当してくれていたチームメイトが)、相当きつかったです。
感想
APIとか外部ライブラリとかは一見魔法のようなものですが、実際に使ってみると得体のしれないものなので、結構沼にはまる可能性があると思います。(現役のエンジニアの方などにとっては常識かもしれませんが、未経験の僕からしたら魔法のようなものでした)恩師の方がよく、「外部ライブラリとかを自分で作れるレベルにならないと使いこなせないよ」とおっしゃっていたのは、今思い出しても本当にその通りで、githubとかソースコード見て仕様を理解して、エラーの原因を考えられるようになる必要があるなと思いました。
また今回初めてAWSを触ってみて、自分的には「AWS触れます」とかゆってしまいそうな気持ちになったのですが、恩師の方に「君はおそらくまだAWSの1%も理解できていないよ」と。恐るべし、という感じですね。
結論、楽しかったからもっとAWSで遊んで見たいと思います。
ご覧いただきありがとうございました。
- 投稿日:2021-01-15T12:27:32+09:00
git init後のデフォルトブランチ名をmainに
概要
ローカルリポジトリのデフォルトブランチ名変更
目的
git init
コマンドで空のGitローカルリポジトリを作成する際のデフォルトブランチ名をmaster
からmain
に変更したい。環境
- ホストトOS
- Windows 10
- 仮想化ツール
- VMware Workstation Player 16.1.0
- ゲストOS
- CentOS 8
- Git
- 変更前
- Git version 2.26.0
- yum で日本の某ミラーサイトから最新取得
- 変更後
- Git version 2.30.0
- 2021年1月15日現在の最新安定板ソース
背景
下記リンクのAWSハンズオンでAWSを実践しようと思い立ち、記載の手順通りに実施しようとした。
上記の手順ではReactプロジェクトをローカルリポジトリとして初期化し、
リモートリポジトリへpushする手順となっている。
git init
後のデフォルトブランチはmaster
。他方、GitHubは昨年秋頃より新規リポジトリのデフォルトブランチ名を
master
からmain
に変更していた。
- GitHubリポジトリ新規作成後のデフォルトブランチは
main
。リモートリポジトリにはない新規ブランチとして
master
をpushしても良かったが、
GitHubの声明を読む感じだと今後はGitのデフォルトブランチはmain
になるのが主流と考えられる。
個人的にmain
を意識づけしたくローカルリポジトリのデフォルトブランチを変更する方法を調査した。過程
- 所要時間 : 2時間
- 調査 : 1時間30分
- 実施 : 30分
- Git version 2.30.0のソースコードをダウンロード
make configure
./configure
make prefix=/usr/local all
make prefix=/usr/local install
- ゲストOS再起動 <= インストールに成功しても再起動しないと反映されなかった。常駐サービスの再起動でも反映可能か?
npx create-react-app amplifyapp
<= 適宜作業ディレクトリにてgit init
<= 6.で作成したamplifyapp
ディレクトリに移動して実行git remote add origin git@github.com:{GitHubアカウント}/{新規作成したリモートリポジトリ名}.git
git add .
git commit -m "{適宜コミットコメント}"
git push origin main
<= AWSハンズオンサイトではブランチ名がmaster
だったのをmain
に変更※途中、
Ed25519
方式の鍵ペアを作成してGitHubのSSH秘密鍵に登録した。割愛。結果
問題なくリモートリポジトリの
main
ブランチに反映できた。
CentOS 8 のyumリポジトリでは、Git最新版が2.27系の模様(2020/01/15現在)。
仕方なくGit公式リポジトリから最新ソースを取得してビルドした。
git config --global init.defaultBranch main
でデフォルトブランチ名をmain
に変更した。
その結果、AWSハンズオンの手順の通りにローカルリポジトリのmainブランチの変更をリモートリポジトリのmainブランチに反映できた。参照 および 引用
- AWS ハンズオン 静的ウェブサイトのホスティング
- The default branch for newly-created repositories is now main
- Gitをソースコードからインストールする
- Change git init default branch name
- Gitリリース
技術情報をご共有頂きましてありがたい限りです。 m(_ _)m
- 投稿日:2021-01-15T08:51:41+09:00
CloudFormationでELBの構築
概要
AWS ELBでWebサーバの負荷分散を実現!【触って覚えるAWS入門】を拝見し、ELBの構築を学びました。
今度はCloudFormationを使い、ELBを構築したので共有します。対象者
- AWSの管理コンソールからELBを構築された経験がある方
- CloudFormationの知見がある方
構築されるもの
- VPC
- InternetGateway
- Subnet
- RouteTable
- EC2
- ELB
- SecurityGroup
- TargetGroup
構成図
セクション説明
セクション 意味 備考 AWSTemplateFormatVersion テンプレートバージョン 2010-09-09 であり、現時点で唯一の有効な値 Description テンプレートを説明するテキスト ---- Metadata テンプレートバージョン ---- Parameters パラメーターの定義 ---- Resources スタックに含める AWS リソースの宣言 ---- Outputs 出力値を宣言 他のスタックでインポートできる 組み込み関数説明
組み込み関数 意味 備考 Ref 指定したパラメータまたはリソースの値 --- Sub 特定した値の入力文字列にある変数の代わりになる 文字列内に変数を挿入する GetAtt テンプレートのリソースから属性の値を返す --- その他用語説明
ロードバランサー
アプリケーションへのトラフィックを複数のターゲットに自動的に分散するサービス。
ターゲット
ELBがトラフィックを転送するEC2インスタンスなどのリソースやエンドポイント。
ターゲットグループ
登録されているターゲットにリクエストをルーティングするための設定
ヘルスチェック
登録されたターゲットに定期的にリクエストを送信するステータスのテスト
リスナー
外部からアクセスするプロトコルやポートの設定
スティッキーセッション
ELBがサーバにリクエスト振り分ける際、特定のCookieを確認することで、特定のクライアントからのリクエストを特定のサーバに紐付けることが出来る機能
テンプレートファイル
下記においてあります。ご自由にお使いください
https://github.com/toyoyuto/cloudformation_albテンプレートファイル(解説付き)
alb.ymlAWSTemplateFormatVersion: "2010-09-09" Description: ALB construction Metadata: # コンソールでパラメータをグループ化およびソートする方法を定義するメタデータキー "AWS::CloudFormation::Interface": # パラメーターグループとそのグループに含めるパラメーターの定義 ParameterGroups: # Project名に関するグループ - Label: default: "Project Name Prefix" Parameters: - PJPrefix # ネットワーク設定に関するグループ - Label: default: "Network Configuration" # 記述された順番に表示される Parameters: - KeyName # パラメーターのラベル ParameterLabels: KeyName: default: "Key Name" # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: PJPrefix: Type: String KeyName: Type: "AWS::EC2::KeyPair::KeyName" Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# # VPC Create VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: "10.0.0.0/16" # VPC に対して DNS 解決がサポートされているか EnableDnsSupport: "true" # VPC 内に起動されるインスタンスが DNS ホスト名を取得するか EnableDnsHostnames: "true" # VPC 内に起動されるインスタンスの許可されているテナンシー InstanceTenancy: default Tags: - Key: Name Value: !Sub "${PJPrefix}-vpc" # InternetGateway Create InternetGateway: Type: "AWS::EC2::InternetGateway" Properties: Tags: - Key: Name Value: !Sub "${PJPrefix}-igw" # IGW Attach InternetGatewayAttachment: Type: "AWS::EC2::VPCGatewayAttachment" Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# # Public1 Subnet Create Public1Subnet: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1a" CidrBlock: "10.0.0.0/24" VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-public1-subnet" # Public2 Subnet Create Public2Subnet: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1c" CidrBlock: "10.0.1.0/24" VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-public2-subnet" # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# # Public1 RouteTable Create Public1RouteTable: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-public1-route" # Public2 RouteTable Create Public2RouteTable: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-public2-route" # ------------------------------------------------------------# # Routing # ------------------------------------------------------------# # Public1 Route Create Public1Route: Type: "AWS::EC2::Route" Properties: RouteTableId: !Ref Public1RouteTable DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # Public2 Route Create Public2Route: Type: "AWS::EC2::Route" Properties: RouteTableId: !Ref Public2RouteTable DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # ------------------------------------------------------------# # RouteTable Associate # ------------------------------------------------------------# # Public1RouteTable Associate PublicSubnet Public1SubnetRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref Public1Subnet RouteTableId: !Ref Public1RouteTable # Public2RouteTable Associate PrivateSubnet Public2SubnetRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref Public2Subnet RouteTableId: !Ref Public2RouteTable # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# # Web1Serverインスタンス Web1Server: Type: AWS::EC2::Instance Properties: ImageId: ami-00f045aed21a55240 KeyName: !Ref KeyName InstanceType: t2.micro NetworkInterfaces: # IPv4 アドレスを割り当てるか - AssociatePublicIpAddress: "true" # ------------------------------------------------------ # アタッチの順序におけるネットワークインターフェイスの位置。 # ネットワークインターフェイスを指定する場合必須 # ------------------------------------------------------ DeviceIndex: "0" SubnetId: !Ref Public1Subnet GroupSet: - !Ref Web1ServerSG # インスタンスの作成時に実行するコマンドなどを記述 UserData: !Base64 | #!/bin/bash yum update -y amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2 yum install -y httpd mariadb-server systemctl start httpd systemctl enable httpd usermod -a -G apache ec2-user chown -R ec2-user:apache /var/www chmod 2775 /var/www find /var/www -type d -exec chmod 2775 {} \; find /var/www -type f -exec chmod 0664 {} \; echo `hostname` > /var/www/html/index.html Tags: - Key: Name Value: !Sub "${PJPrefix}-web1-server" # Web1Serverセキュリティグループ Web1ServerSG: Type: AWS::EC2::SecurityGroup Properties: GroupName: web1-sg-cf GroupDescription: web1 server sg VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-web1-server-sg" # Web1ServerSGのインプットルール Web1ServerSGIngress: Type: "AWS::EC2::SecurityGroupIngress" Properties: IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !GetAtt [ ALBSecurityGroup, GroupId ] GroupId: !GetAtt [ Web1ServerSG, GroupId ] # Web2Serverインスタンス Web2Server: Type: AWS::EC2::Instance Properties: ImageId: ami-00f045aed21a55240 KeyName: !Ref KeyName InstanceType: t2.micro NetworkInterfaces: # IPv4 アドレスを割り当てるか - AssociatePublicIpAddress: "true" # ------------------------------------------------------ # アタッチの順序におけるネットワークインターフェイスの位置。 # ネットワークインターフェイスを指定する場合必須 # ------------------------------------------------------ DeviceIndex: "0" SubnetId: !Ref Public2Subnet GroupSet: - !Ref Web2ServerSG # インスタンスの作成時に実行するコマンドなどを記述 UserData: !Base64 | #!/bin/bash yum update -y amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2 yum install -y httpd mariadb-server systemctl start httpd systemctl enable httpd usermod -a -G apache ec2-user chown -R ec2-user:apache /var/www chmod 2775 /var/www find /var/www -type d -exec chmod 2775 {} \; find /var/www -type f -exec chmod 0664 {} \; echo `hostname` > /var/www/html/index.html Tags: - Key: Name Value: !Sub "${PJPrefix}-web2-server" # Web2Serverセキュリティグループ Web2ServerSG: Type: AWS::EC2::SecurityGroup Properties: GroupName: web2-sg-cf GroupDescription: web2 server sg VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-web2-server-sg" # Web2ServerSGのインプットルール Web2ServerSGIngress: Type: "AWS::EC2::SecurityGroupIngress" Properties: IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !GetAtt [ ALBSecurityGroup, GroupId ] GroupId: !GetAtt [ Web2ServerSG, GroupId ] # ------------------------------------------------------------# # Target Group # ------------------------------------------------------------# TargetGroup: Type: "AWS::ElasticLoadBalancingV2::TargetGroup" Properties: VpcId: !Ref VPC Name: !Sub "${PJPrefix}-tg" # ターゲットにトラフィックをルーティングするために使用するプロトコル Protocol: HTTP Port: 80 # ターゲットでヘルスチェックを実行するときにロードバランサーが使用するプロトコル HealthCheckProtocol: HTTP # ヘルスチェックのターゲットの送信先である HealthCheckPath: "/" # ターゲットでヘルスチェックを実行するときにロードバランサーが使用するポート HealthCheckPort: "traffic-port" # 非正常なインスタンスが正常であると見なすまでに必要なヘルスチェックの連続成功回数 HealthyThresholdCount: 2 # ターゲットが異常であると見なされるまでに必要なヘルスチェックの連続失敗回数 UnhealthyThresholdCount: 2 # ヘルスチェックを失敗と見なす、ターゲットからレスポンスがない時間 HealthCheckTimeoutSeconds: 5 # 個々のターゲットのヘルスチェックの概算間隔 HealthCheckIntervalSeconds: 10 # ターゲットからの正常なレスポンスを確認するために使用する HTTP コード Matcher: HttpCode: 200 Tags: - Key: Name Value: !Sub "${PJPrefix}-tg" # ターゲットグループの属性 TargetGroupAttributes: # 登録解除するターゲットの状態が draining から unused に変わるのをELBが待機する時間 - Key: "deregistration_delay.timeout_seconds" Value: 300 # スティッキーセッションが有効か¥ - Key: "stickiness.enabled" Value: false # スティッキーセッションのタイプ - Key: "stickiness.type" Value: lb_cookie # クライアントからのリクエストを同じターゲットにルーティングする必要がある期間 - Key: "stickiness.lb_cookie.duration_seconds" Value: 86400 Targets: - Id: !Ref Web1Server Port: 80 - Id: !Ref Web2Server Port: 80 # ------------------------------------------------------------# # ALB # ------------------------------------------------------------# InternetALB: Type: "AWS::ElasticLoadBalancingV2::LoadBalancer" Properties: Name: !Sub "${PJPrefix}-alb" Tags: - Key: Name Value: !Sub "${PJPrefix}-alb" # 内部向けかインターネット向け Scheme: "internet-facing" # ロードバランサーの属性 LoadBalancerAttributes: # 削除保護が有効化されているかどうかを示します - Key: "deletion_protection.enabled" Value: false # アイドルタイムアウト値 - Key: "idle_timeout.timeout_seconds" Value: 60 SecurityGroups: - !Ref ALBSecurityGroup Subnets: - !Ref Public1Subnet - !Ref Public2Subnet ALBListener: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: # デフォルトルールのアクション DefaultActions: - TargetGroupArn: !Ref TargetGroup # ルールアクションタイプ # forwardは指定されたターゲットグループにリクエストを転送 Type: forward LoadBalancerArn: !Ref InternetALB Port: 80 Protocol: HTTP # InternetALBのセキュリティグループ ALBSecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: VpcId: !Ref VPC GroupName: !Sub "${PJPrefix}-alb-sg" GroupDescription: "-" Tags: - Key: "Name" Value: !Sub "${PJPrefix}-alb-sg" # ALBSecurityGroupのインプットルール ALBSecurityGroupIngress: Type: "AWS::EC2::SecurityGroupIngress" Properties: IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: "0.0.0.0/0" GroupId: !GetAtt [ ALBSecurityGroup, GroupId ] # ALBSecurityGroupのアウトプットルール ALBSecurityGroupEgress1: Type: "AWS::EC2::SecurityGroupEgress" Properties: IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !GetAtt [ Web1ServerSG, GroupId ] GroupId: !GetAtt [ ALBSecurityGroup, GroupId ] ALBSecurityGroupEgress2: Type: "AWS::EC2::SecurityGroupEgress" Properties: IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !GetAtt [ Web2ServerSG, GroupId ] GroupId: !GetAtt [ ALBSecurityGroup, GroupId ] # ------------------------------------------------------------# # Output Parameters # ------------------------------------------------------------# Outputs: # VPC VPC: Value: !Ref VPC Export: Name: !Sub "${PJPrefix}-vpc" # Subnet Public1Subnet: Value: !Ref Public1Subnet Export: Name: !Sub "${PJPrefix}-public1-subnet" Public2Subnet: Value: !Ref Public2Subnet Export: Name: !Sub "${PJPrefix}-public2-subnet" # EC2 Web1Server: Value: !Ref Web1Server Export: Name: !Sub "${PJPrefix}-web1-server" Web2Server: Value: !Ref Web2Server Export: Name: !Sub "${PJPrefix}-web2-server" # ALB InternetALB: Value: !Ref InternetALB Export: Name: !Sub "${PJPrefix}-alb"参考
AWS ELBでWebサーバの負荷分散を実現!【触って覚えるAWS入門】
AWS再入門ブログリレー Elastic Load Balancing編
AWS ALB の設定方法は ? リスナー ? ターゲットグループ ?
【基礎から学ぶ】ELBのスティッキーセッションについてまとめてみた
- 投稿日:2021-01-15T03:29:36+09:00
Lambda Container Image を Java 15ベースで作ってみる
概要
AWS Lambdaがコンテナイメージをサポートしました。
コンテナイメージ作成には、AWSの提供しているベースイメージを使うのが手っ取り早いです。
が、2021/01/15現在、Java15は提供されていません。私の参画しているプロジェクトでは現在Java15を採用しており、
11だと色々めんどくさいので15ベースでコンテナイメージを作れ! と言われ、
俺には無理だろ...と思いながら色々調べてやったら動いたので、共有します。ソースコード
早速ですが成果物としてのソースコードを公開します。
多分動くと思います。
200 OK
を返すだけのサンプルです。
intx24/java-15-lambda-containerHandlerの処理はほとんど awsdocs/aws-lambda-developer-guideのサンプル から拝借しています。
変更点として、Java15対応を確認するため、不要なswitch式を付け足しました。どうやったか
1. まずJava11ベースイメージで動かしてみる
Java11のベースイメージを元に、まず動かしてみます。
ありがたいことに関連記事があったため、参考にして丸パクリし、Java11ベースのLambdaイメージを作成しました。
AWS LambdaのコンテナサポートをJavaで試してみた。2. Java11ベースイメージDockerfileを見てみる
「Java11ベースイメージのDockerfileを参考にしてみましょう」と上司に言われたので、見てみます。
よくわからない
tar.xz
を大量にADDしていることがわかります。
git lfs pull
することでtar.xz
ファイルが解凍可能になります。(最初これがわからなくて詰んだと思った)
以下、理解していったことを書いていきますlambda-entrypoint.sh
DockerfileのENTRYPOINTです。
どうやら
/var/runtime/bootstrap
が実行されるらしいです。
${AWS_LAMBDA_RUNTIME_API}
がない場合(多分ローカル環境)では エミュレータを使ってローカル実行を可能にしているっぽい。エミュレータに関しては、Runtime support for Lambda container images
からダウンロード出来るので、これをローカルからコンテナにCOPYすれば良さそうです。lambda-entrypoint.sh#!/bin/sh # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. if [ $# -ne 1 ]; then echo "entrypoint requires the handler name to be the first argument" 1>&2 exit 142 fi export _HANDLER="$1" RUNTIME_ENTRYPOINT=/var/runtime/bootstrap if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then exec /usr/local/bin/aws-lambda-rie $RUNTIME_ENTRYPOINT else exec $RUNTIME_ENTRYPOINT fi/var/runtime/bootstrap
よくわからないファイルでした。
コンテナにコピペしても動かなかった & 中身がブラックボックス過ぎと判断したので、代替手段を探します。
どうやらカスタムランタイムを作る手順でbootstrapを作ればいけるらしい?カスタムランタイムについて調べつつ、無理だから諦めようと思った頃に、
aws-lambda-java-runtime-interface-client でLambda関数が実行できることを知りました。
このコマンドをaws-lambda-rie
の引数にすれば良さそうです。3. 実装してみる
Dockerfile
Java15が入ってるAmazonLinux2のイメージがあるのでこれをベースにします。
後述する、自作のlambda-entrypoint.sh
をENTRYPOINTとして,
Lambda関数example.Handler::handleRequest
を渡します。FROM amazoncorretto:15 # set environment variables ENV CLASSPATH /var/task/* WORKDIR /var/task # copy lambda execution files COPY aws-lambda-rie /usr/local/bin/aws-lambda-rie COPY lambda-entrypoint.sh /lambda-entrypoint.sh # Copy function code COPY build/libs/java-15-lambda-container-1.0-SNAPSHOT-all.jar /var/task/ # Set the Entrypoint ENTRYPOINT ["/lambda-entrypoint.sh"] # Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) CMD [ "example.Handler::handleRequest" ]lambda-entrypoint.sh
aws-lambda-java-runtime-interface-client
を実行します。
$HANDLER_NAME
には、 DockerfileのCMD
で指定したLambda関数が渡されます。
aws-lambda-java-runtime-interface-client
の README通りにjava -cp ./* com.amazonaws.services.lambda.runtime.api.client.AWSLambda example.App::sayHello
を引数として渡すと、
aws-lambda-rie
がjava
をbootstrap
のあるパスだと解釈して死ぬため、/usr/bin/java
に置き換えます。また、ここではクラスパスをDockerfile内で環境変数として指定済みです。
#!/bin/sh if [ $# -ne 1 ]; then echo "entrypoint requires the handler name to be the first argument" 1>&2 exit 142 fi HANDLER_NAME=$1 if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then # local environment exec /usr/local/bin/aws-lambda-rie \ /usr/bin/java \ com.amazonaws.services.lambda.runtime.api.client.AWSLambda "$HANDLER_NAME" else exec /usr/bin/java \ com.amazonaws.services.lambda.runtime.api.client.AWSLambda "$HANDLER_NAME" fi/var/runtime/bootstrap
lambda-entrypoint.sh
内に統合したので不要となりました。苦労したこと
javaのビルド周りがわからん
java自体の経験が1年ほど + intelliJ経由で触ってた + プロトタイプが出来ているプロジェクトに途中参画した 人間なので、
javaはgradleタスク実行したらとりあえず動くぐらいの認識でした。そもそもクラスパスを通さなければ実行できないということも知らず,
Class Not Found
エラーで半日ほど消耗しました。
同じ原因で、カスタムランタイムのビルドについても1分で諦めました。AWS提供のベースイメージを
run -it
オプションでコンテナを探索できない最初は
ENTRYPOINT
の概念すら知らなかったため、
各種ファイルを確認するため、run -it
でコンテナを探索しようと思ったら出来ず困ってました。
結果的にはdocker exec
経由でcat
したり、
IntelliJのdocker のFilesタブで確認していました。まとめ
今回、Lambda Container Image を Java 15ベースで作ってみて、一応動くところまで出来ました。
調査・実装含めて4日ほど作業にあてましたが、
まだまだ知るべきことが多くあるなと思わされる経験でした。