20210115のAWSに関する記事は6件です。

CodePipelineのbuildのレビューを一気に承認したい

起きた状況

stg環境での作業が終わり、いよいよ本番の準備をしようとなり、本番のcodepipelineを見ると、ビルドのレビューを一つも承認してなくて、一つ一つ承認していかないといけない、、
先輩に聞くと解決方法を教えていただいたので、メモがわりに記載

解決方法

流れ
編集→そのまま保存→変更をリリースする→レビューを承認

パイプライン内の
スクリーンショット 2021-01-15 16.35.22.png
編集で
スクリーンショット 2021-01-15 16.35.28.png
何も変えずに保存
スクリーンショット 2021-01-15 16.35.40.png
変更をリリース
スクリーンショット 2021-01-15 16.35.28.png
最後にレビューを承認

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterでAmplify Datastoreを使ってみる

お客様からの強いご要望でバックエンドはAWSの利用必須。Flutter使いたいのにライブラリが用意されていないので泣く泣くReact Nativeで実装するしかない...なんてことありますよね。
そんな方に朗報です。dev版ですが、appsync用のflutterライブラリがついに出ました!
ということで使ってみました。

https://docs.amplify.aws/lib/datastore/getting-started/q/platform/flutter

公式の手順に従って実装してみましたがいくつか記述に不足があるようなのでメモを残しておきます。

作成したのは、いつものカウンターアプリのボタンを押すとDynamodbにデータが登録されるシンプルなやつです。

image.png

プロジェクト作成

いつものようにプロジェクト作ります。
image.png

image.png

image.png

ライブラリ導入

pubspec.yamlファイルに以下の記述を追記します。
公式にはamplify_coreは記載がありませんが、必要なようです。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  amplify_datastore: any
  amplify_core: any

Amplify 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というファイルが作成されていることが確認できます。
image.png

Amplify Datastoreの導入

公式のサンプルがinitStateで初期化しているのでそれを参考に、main.dartにAmplifyDataStoreを導入します。
(StatefulWidgetクラスはinitStateメソッド持っていないはずなのでサンプルはおそらく誤り?)

main.dart
import '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
    }

再度ビルドすると無事アプリが起動しました。

image.png

右下のボタンをクリックし、Dynamodbを確認するとデータが登録されていることが確認できました。

image.png

公式の手順だけだと足りないので、もう少し記載があるとうれしいな。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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で遊んで見たいと思います。

ご覧いただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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分
  1. Git version 2.30.0のソースコードをダウンロード
  2. make configure
  3. ./configure
  4. make prefix=/usr/local all
  5. make prefix=/usr/local install
  6. ゲストOS再起動 <= インストールに成功しても再起動しないと反映されなかった。常駐サービスの再起動でも反映可能か?
  7. npx create-react-app amplifyapp <= 適宜作業ディレクトリにて
  8. git init <= 6.で作成した amplifyapp ディレクトリに移動して実行
  9. git remote add origin git@github.com:{GitHubアカウント}/{新規作成したリモートリポジトリ名}.git
  10. git add .
  11. git commit -m "{適宜コミットコメント}"
  12. 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ブランチに反映できた。

参照 および 引用

技術情報をご共有頂きましてありがたい限りです。 m(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CloudFormationでELBの構築

概要

AWS ELBでWebサーバの負荷分散を実現!【触って覚えるAWS入門】を拝見し、ELBの構築を学びました。
今度はCloudFormationを使い、ELBを構築したので共有します。

対象者

  • AWSの管理コンソールからELBを構築された経験がある方
  • CloudFormationの知見がある方

構築されるもの

  • VPC
  • InternetGateway
  • Subnet
  • RouteTable
  • EC2
  • ELB
  • SecurityGroup
  • TargetGroup

構成図

elb (1).png

セクション説明

セクション 意味 備考
AWSTemplateFormatVersion テンプレートバージョン 2010-09-09 であり、現時点で唯一の有効な値
Description テンプレートを説明するテキスト ----
Metadata テンプレートバージョン ----
Parameters パラメーターの定義 ----
Resources スタックに含める AWS リソースの宣言 ----
Outputs 出力値を宣言 他のスタックでインポートできる

組み込み関数説明

組み込み関数 意味 備考
Ref 指定したパラメータまたはリソースの値 ---
Sub 特定した値の入力文字列にある変数の代わりになる 文字列内に変数を挿入する
GetAtt テンプレートのリソースから属性の値を返す ---

その他用語説明

ロードバランサー

アプリケーションへのトラフィックを複数のターゲットに自動的に分散するサービス。

ターゲット

ELBがトラフィックを転送するEC2インスタンスなどのリソースやエンドポイント。

ターゲットグループ

登録されているターゲットにリクエストをルーティングするための設定

ヘルスチェック

登録されたターゲットに定期的にリクエストを送信するステータスのテスト

リスナー

外部からアクセスするプロトコルやポートの設定

スティッキーセッション

ELBがサーバにリクエスト振り分ける際、特定のCookieを確認することで、特定のクライアントからのリクエストを特定のサーバに紐付けることが出来る機能

テンプレートファイル

下記においてあります。ご自由にお使いください
https://github.com/toyoyuto/cloudformation_alb

テンプレートファイル(解説付き)

alb.yml
AWSTemplateFormatVersion: 
  "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のスティッキーセッションについてまとめてみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda Container Image を Java 15ベースで作ってみる

概要

AWS Lambdaがコンテナイメージをサポートしました。

コンテナイメージ作成には、AWSの提供しているベースイメージを使うのが手っ取り早いです。
が、2021/01/15現在、Java15は提供されていません。

私の参画しているプロジェクトでは現在Java15を採用しており、
11だと色々めんどくさいので15ベースでコンテナイメージを作れ! と言われ、
俺には無理だろ...と思いながら色々調べてやったら動いたので、共有します。

ソースコード

早速ですが成果物としてのソースコードを公開します。
多分動くと思います。

200 OK を返すだけのサンプルです。
intx24/java-15-lambda-container

Handlerの処理はほとんど 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-riejavabootstrap のあるパスだと解釈して死ぬため、/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日ほど作業にあてましたが、
まだまだ知るべきことが多くあるなと思わされる経験でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む